Merge branch 'release/25.07.0' into main
This commit is contained in:
commit
86ec2f5ea5
1075 changed files with 9425 additions and 3995 deletions
1
.github/workflows/quality.yml
vendored
1
.github/workflows/quality.yml
vendored
|
|
@ -270,7 +270,6 @@ jobs:
|
|||
- name: Run shellcheck
|
||||
uses: ludeeus/action-shellcheck@2.0.0
|
||||
with:
|
||||
scandir: ./tools
|
||||
severity: warning
|
||||
|
||||
upload_reports:
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ mkdir -p /data/local/tmp/recordings;
|
|||
FILENAME=/data/local/tmp/recordings/testRecording$COUNT.mp4
|
||||
while true
|
||||
do
|
||||
((COUNT++))
|
||||
COUNT=$((COUNT+1))
|
||||
FILENAME=/data/local/tmp/recordings/testRecording$COUNT.mp4
|
||||
echo "\nRecording video file #$COUNT"
|
||||
printf "\nRecording video file #%d\n" $COUNT
|
||||
screenrecord --bugreport --bit-rate=16m --size 720x1280 $FILENAME
|
||||
done
|
||||
|
|
|
|||
2
.idea/kotlinc.xml
generated
2
.idea/kotlinc.xml
generated
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="2.1.21" />
|
||||
<option name="version" value="2.2.0" />
|
||||
</component>
|
||||
</project>
|
||||
42
CHANGES.md
42
CHANGES.md
|
|
@ -1,3 +1,45 @@
|
|||
Changes in Element X v25.06.3
|
||||
=============================
|
||||
|
||||
## What's Changed
|
||||
### ✨ Features
|
||||
* Feature : room version upgrade by @ganfra in https://github.com/element-hq/element-x-android/pull/4862
|
||||
* Add a developer option for history sharing on invite by @richvdh in https://github.com/element-hq/element-x-android/pull/4821
|
||||
### 🙌 Improvements
|
||||
* Change : add tombstoned room decoration by @ganfra in https://github.com/element-hq/element-x-android/pull/4891
|
||||
* Show generic notification when Event cannot be resolved by @bmarty in https://github.com/element-hq/element-x-android/pull/4889
|
||||
### 🐛 Bugfixes
|
||||
* [a11y] Improve screen reader on polls by @bmarty in https://github.com/element-hq/element-x-android/pull/4875
|
||||
* fix (event action): allow to edit only if permission to send message by @ganfra in https://github.com/element-hq/element-x-android/pull/4895
|
||||
* fix (room upgrade) : room predecessor banner on DM room by @ganfra in https://github.com/element-hq/element-x-android/pull/4896
|
||||
* fix (join room) : do not navigate up when join is successful by @ganfra in https://github.com/element-hq/element-x-android/pull/4899
|
||||
### 🗣 Translations
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/4842
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/4881
|
||||
### Dependency upgrades
|
||||
* chore(deps): update plugin dependencycheck to v12.1.3 by @renovate in https://github.com/element-hq/element-x-android/pull/4856
|
||||
* fix(deps): update dependency org.maplibre.gl:android-sdk to v11.10.2 by @renovate in https://github.com/element-hq/element-x-android/pull/4858
|
||||
* fix(deps): update kotlin to v2.1.21-2.0.2 by @renovate in https://github.com/element-hq/element-x-android/pull/4850
|
||||
* fix(deps): update dependency app.cash.turbine:turbine to v1.2.1 by @renovate in https://github.com/element-hq/element-x-android/pull/4865
|
||||
* Update dependency com.posthog:posthog-android to v3.18.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4873
|
||||
* Update dependency org.maplibre.gl:android-sdk to v11.10.3 by @renovate in https://github.com/element-hq/element-x-android/pull/4879
|
||||
* fix(deps): update dependency com.posthog:posthog-android to v3.19.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4882
|
||||
* fix(deps): update dependency io.sentry:sentry-android to v8.13.3 by @renovate in https://github.com/element-hq/element-x-android/pull/4870
|
||||
* fix(deps): update showkase to v1.0.4 by @renovate in https://github.com/element-hq/element-x-android/pull/4878
|
||||
* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.6.18 by @renovate in https://github.com/element-hq/element-x-android/pull/4894
|
||||
### Others
|
||||
* Annotate Composable functions with `@ReadOnlyComposable` where it's possible by @bmarty in https://github.com/element-hq/element-x-android/pull/4859
|
||||
* Add documentation on WebViewPipController by @bmarty in https://github.com/element-hq/element-x-android/pull/4861
|
||||
* Small cleanup around log tag. by @bmarty in https://github.com/element-hq/element-x-android/pull/4860
|
||||
* Another cleanup by @bmarty in https://github.com/element-hq/element-x-android/pull/4869
|
||||
* Disable BT audio devices for Element Call on Android < 12 by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4876
|
||||
* Add a banner to ask the user to disable battery optimization when Event cannot be resolved from Push by @bmarty in https://github.com/element-hq/element-x-android/pull/4845
|
||||
* a11y: improve accessibility on rich text editor options. by @bmarty in https://github.com/element-hq/element-x-android/pull/4886
|
||||
* A11Y: improve accessibility on event reactions. by @bmarty in https://github.com/element-hq/element-x-android/pull/4877
|
||||
|
||||
|
||||
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v25.06.2...v25.06.3
|
||||
|
||||
Changes in Element X v25.06.2
|
||||
=============================
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ Learn more about why we are building Element X in our blog post: [https://elemen
|
|||
* [Translations](#translations)
|
||||
* [Rust SDK](#rust-sdk)
|
||||
* [Status](#status)
|
||||
* [Minimum SDK version](#minimum-sdk-version)
|
||||
* [Contributing](#contributing)
|
||||
* [Build instructions](#build-instructions)
|
||||
* [Support](#support)
|
||||
|
|
@ -73,6 +74,12 @@ We're doing this as a way to share code between platforms and while we've seen p
|
|||
|
||||
This project is in an early rollout and migration phase.
|
||||
|
||||
## Minimum SDK version
|
||||
|
||||
Element X Android requires a minimum SDK version of 24 (Android 7.0, Nougat). We aim to support devices running Android 7.0 and above, which covers a wide range of devices still in use today.
|
||||
|
||||
Element Android Enterprise requires a minimum SDK version of 33 (Android 13, Tiramisu). For Element Enterprise, we support only devices that still receive security updates, which means devices running Android 13 and above. Android does not have a documented support policy, but some information can be found at [https://endoflife.date/android](https://endoflife.date/android).
|
||||
|
||||
## Contributing
|
||||
|
||||
Want to get actively involved in the project? You're more than welcome! A good way to start is to check the issues that are labelled with the [good first issue](https://github.com/element-hq/element-x-android/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) label. Let us know by commenting the issue that you're starting working on it.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
<locale android:name="bg"/>
|
||||
<locale android:name="cs"/>
|
||||
<locale android:name="cy"/>
|
||||
<locale android:name="da"/>
|
||||
<locale android:name="de"/>
|
||||
<locale android:name="el"/>
|
||||
<locale android:name="en"/>
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ import dagger.assisted.AssistedInject
|
|||
import im.vector.app.features.analytics.plan.JoinedRoom
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.appnav.loggedin.LoggedInNode
|
||||
import io.element.android.appnav.loggedin.MediaPreviewConfigMigration
|
||||
import io.element.android.appnav.loggedin.SendQueues
|
||||
import io.element.android.appnav.room.RoomFlowNode
|
||||
import io.element.android.appnav.room.RoomNavigationTarget
|
||||
|
|
@ -49,11 +50,11 @@ import io.element.android.features.createroom.api.CreateRoomEntryPoint
|
|||
import io.element.android.features.ftue.api.FtueEntryPoint
|
||||
import io.element.android.features.ftue.api.state.FtueService
|
||||
import io.element.android.features.ftue.api.state.FtueState
|
||||
import io.element.android.features.home.api.HomeEntryPoint
|
||||
import io.element.android.features.logout.api.LogoutEntryPoint
|
||||
import io.element.android.features.preferences.api.PreferencesEntryPoint
|
||||
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||
import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint
|
||||
import io.element.android.features.roomlist.api.RoomListEntryPoint
|
||||
import io.element.android.features.securebackup.api.SecureBackupEntryPoint
|
||||
import io.element.android.features.share.api.ShareEntryPoint
|
||||
import io.element.android.features.userprofile.api.UserProfileEntryPoint
|
||||
|
|
@ -90,6 +91,15 @@ import java.time.Duration
|
|||
import java.time.Instant
|
||||
import java.util.Optional
|
||||
import java.util.UUID
|
||||
import kotlin.collections.List
|
||||
import kotlin.collections.any
|
||||
import kotlin.collections.emptyList
|
||||
import kotlin.collections.first
|
||||
import kotlin.collections.forEach
|
||||
import kotlin.collections.listOf
|
||||
import kotlin.collections.mapNotNull
|
||||
import kotlin.collections.plus
|
||||
import kotlin.collections.setOf
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlin.time.toKotlinDuration
|
||||
|
|
@ -98,7 +108,7 @@ import kotlin.time.toKotlinDuration
|
|||
class LoggedInFlowNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val roomListEntryPoint: RoomListEntryPoint,
|
||||
private val homeEntryPoint: HomeEntryPoint,
|
||||
private val preferencesEntryPoint: PreferencesEntryPoint,
|
||||
private val createRoomEntryPoint: CreateRoomEntryPoint,
|
||||
private val appNavigationStateService: AppNavigationStateService,
|
||||
|
|
@ -114,6 +124,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||
private val sendingQueue: SendQueues,
|
||||
private val logoutEntryPoint: LogoutEntryPoint,
|
||||
private val incomingVerificationEntryPoint: IncomingVerificationEntryPoint,
|
||||
private val mediaPreviewConfigMigration: MediaPreviewConfigMigration,
|
||||
snackbarDispatcher: SnackbarDispatcher,
|
||||
) : BaseFlowNode<LoggedInFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
|
|
@ -160,7 +171,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||
// Otherwise, the RoomList UI may be incorrectly displayed on top
|
||||
withTimeout(5.seconds) {
|
||||
backstack.elements.first { elements ->
|
||||
elements.any { it.key.navTarget == NavTarget.RoomList }
|
||||
elements.any { it.key.navTarget == NavTarget.Home }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -179,13 +190,14 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||
appNavigationStateService.onNavigateToSpace(id, MAIN_SPACE)
|
||||
loggedInFlowProcessor.observeEvents(sessionCoroutineScope)
|
||||
matrixClient.sessionVerificationService().setListener(verificationListener)
|
||||
mediaPreviewConfigMigration()
|
||||
|
||||
ftueService.state
|
||||
.onEach { ftueState ->
|
||||
when (ftueState) {
|
||||
is FtueState.Unknown -> Unit // Nothing to do
|
||||
is FtueState.Incomplete -> backstack.safeRoot(NavTarget.Ftue)
|
||||
is FtueState.Complete -> backstack.safeRoot(NavTarget.RoomList)
|
||||
is FtueState.Complete -> backstack.safeRoot(NavTarget.Home)
|
||||
}
|
||||
}
|
||||
.launchIn(lifecycleScope)
|
||||
|
|
@ -212,7 +224,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||
data object LoggedInPermanent : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data object RoomList : NavTarget
|
||||
data object Home : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data class Room(
|
||||
|
|
@ -269,8 +281,8 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||
}
|
||||
createNode<LoggedInNode>(buildContext, listOf(callback))
|
||||
}
|
||||
NavTarget.RoomList -> {
|
||||
val callback = object : RoomListEntryPoint.Callback {
|
||||
NavTarget.Home -> {
|
||||
val callback = object : HomeEntryPoint.Callback {
|
||||
override fun onRoomClick(roomId: RoomId) {
|
||||
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias()))
|
||||
}
|
||||
|
|
@ -303,7 +315,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||
backstack.push(NavTarget.LogoutForNativeSlidingSyncMigrationNeeded)
|
||||
}
|
||||
}
|
||||
roomListEntryPoint
|
||||
homeEntryPoint
|
||||
.nodeBuilder(this, buildContext)
|
||||
.callback(callback)
|
||||
.build()
|
||||
|
|
@ -487,7 +499,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||
clearBackstack: Boolean,
|
||||
) {
|
||||
waitForNavTargetAttached { navTarget ->
|
||||
navTarget is NavTarget.RoomList
|
||||
navTarget is NavTarget.Home
|
||||
}
|
||||
attachChild<RoomFlowNode> {
|
||||
val roomNavTarget = NavTarget.Room(
|
||||
|
|
@ -504,7 +516,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||
|
||||
suspend fun attachUser(userId: UserId) {
|
||||
waitForNavTargetAttached { navTarget ->
|
||||
navTarget is NavTarget.RoomList
|
||||
navTarget is NavTarget.Home
|
||||
}
|
||||
attachChild<Node> {
|
||||
backstack.push(
|
||||
|
|
@ -517,7 +529,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
|||
|
||||
internal suspend fun attachIncomingShare(intent: Intent) {
|
||||
waitForNavTargetAttached { navTarget ->
|
||||
navTarget is NavTarget.RoomList
|
||||
navTarget is NavTarget.Home
|
||||
}
|
||||
attachChild<Node> {
|
||||
backstack.push(
|
||||
|
|
@ -555,7 +567,7 @@ private class AttachRoomOperation(
|
|||
return if (clearBackstack) {
|
||||
// Makes sure the room list target is alone in the backstack and stashed
|
||||
elements.mapNotNull { element ->
|
||||
if (element.key.navTarget == LoggedInFlowNode.NavTarget.RoomList) {
|
||||
if (element.key.navTarget == LoggedInFlowNode.NavTarget.Home) {
|
||||
element.transitionTo(STASHED, this)
|
||||
} else {
|
||||
null
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.appnav.loggedin
|
||||
|
||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||
import io.element.android.libraries.matrix.api.media.MediaPreviewService
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* This migration is temporary, will be safe to remove after some time.
|
||||
* The goal is to set the server config if it's not set, and remove the local data.
|
||||
*/
|
||||
class MediaPreviewConfigMigration @Inject constructor(
|
||||
private val mediaPreviewService: MediaPreviewService,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
@SessionCoroutineScope
|
||||
private val sessionCoroutineScope: CoroutineScope,
|
||||
) {
|
||||
@Suppress("DEPRECATION")
|
||||
operator fun invoke() = sessionCoroutineScope.launch {
|
||||
val hideInviteAvatars = appPreferencesStore.getHideInviteAvatarsFlow().first()
|
||||
val mediaPreviewValue = appPreferencesStore.getTimelineMediaPreviewValueFlow().first()
|
||||
if (hideInviteAvatars == null && mediaPreviewValue == null) {
|
||||
// No local data, abort.
|
||||
return@launch
|
||||
}
|
||||
mediaPreviewService
|
||||
.fetchMediaPreviewConfig()
|
||||
.onSuccess { config ->
|
||||
if (config != null) {
|
||||
appPreferencesStore.setHideInviteAvatars(null)
|
||||
appPreferencesStore.setTimelineMediaPreviewValue(null)
|
||||
} else {
|
||||
if (hideInviteAvatars != null) {
|
||||
mediaPreviewService.setHideInviteAvatars(hideInviteAvatars)
|
||||
appPreferencesStore.setHideInviteAvatars(null)
|
||||
}
|
||||
if (mediaPreviewValue != null) {
|
||||
mediaPreviewService.setMediaPreviewValue(mediaPreviewValue)
|
||||
appPreferencesStore.setTimelineMediaPreviewValue(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onFailure {
|
||||
Timber.e(it, "Couldn't perform migration, failed to fetch media preview config.")
|
||||
}
|
||||
}
|
||||
}
|
||||
6
appnav/src/main/res/values-da/translations.xml
Normal file
6
appnav/src/main/res/values-da/translations.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="banner_migrate_to_native_sliding_sync_action">"Log ud og opgradér"</string>
|
||||
<string name="banner_migrate_to_native_sliding_sync_app_force_logout_title">"%1$s understøtter ikke længere den gamle protokol. Log ud og log ind igen for at fortsætte med at bruge appen."</string>
|
||||
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Din hjemmeserver understøtter ikke længere den gamle protokol. Log ud og log ind igen for at fortsætte med at bruge appen."</string>
|
||||
</resources>
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="banner_migrate_to_native_sliding_sync_action">"Keluar & Tingkatkan"</string>
|
||||
<string name="banner_migrate_to_native_sliding_sync_app_force_logout_title">"%1$s tidak lagi mendukung protokol lama. Silakan keluar dan masuk kembali untuk terus menggunakan aplikasi."</string>
|
||||
<string name="banner_migrate_to_native_sliding_sync_force_logout_title">"Homeserver Anda tidak lagi mendukung protokol lama. Silakan keluar dan masuk kembali untuk terus menggunakan aplikasi."</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION")
|
||||
|
||||
package io.element.android.appnav.loggedin
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.media.MediaPreviewConfig
|
||||
import io.element.android.libraries.matrix.api.media.MediaPreviewValue
|
||||
import io.element.android.libraries.matrix.test.media.FakeMediaPreviewService
|
||||
import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class MediaPreviewConfigMigrationTest {
|
||||
@Test
|
||||
fun `when no local data exists, migration does nothing`() = runTest {
|
||||
val appPreferencesStore = InMemoryAppPreferencesStore()
|
||||
val mediaPreviewService = FakeMediaPreviewService(
|
||||
fetchMediaPreviewConfigResult = { Result.success(null) }
|
||||
)
|
||||
val migration = createMigration(appPreferencesStore, mediaPreviewService)
|
||||
|
||||
migration().join()
|
||||
|
||||
// Verify no calls were made to set server config
|
||||
// since there's nothing to migrate
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when local data exists and server has config, clears local data`() = runTest {
|
||||
val appPreferencesStore = InMemoryAppPreferencesStore().apply {
|
||||
setHideInviteAvatars(true)
|
||||
setTimelineMediaPreviewValue(MediaPreviewValue.Private)
|
||||
}
|
||||
val serverConfig = MediaPreviewConfig(
|
||||
hideInviteAvatar = false,
|
||||
mediaPreviewValue = MediaPreviewValue.On
|
||||
)
|
||||
val mediaPreviewService = FakeMediaPreviewService(
|
||||
fetchMediaPreviewConfigResult = { Result.success(serverConfig) }
|
||||
)
|
||||
val migration = createMigration(appPreferencesStore, mediaPreviewService)
|
||||
|
||||
migration().join()
|
||||
|
||||
// Verify local data was cleared
|
||||
assertThat(appPreferencesStore.getHideInviteAvatarsFlow().first()).isNull()
|
||||
assertThat(appPreferencesStore.getTimelineMediaPreviewValueFlow().first()).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when local hideInviteAvatars exists and server has no config, migrates to server`() = runTest {
|
||||
val appPreferencesStore = InMemoryAppPreferencesStore().apply {
|
||||
setHideInviteAvatars(true)
|
||||
}
|
||||
var setHideInviteAvatarsValue: Boolean? = null
|
||||
val mediaPreviewService = FakeMediaPreviewService(
|
||||
fetchMediaPreviewConfigResult = { Result.success(null) },
|
||||
setHideInviteAvatarsResult = { value ->
|
||||
setHideInviteAvatarsValue = value
|
||||
Result.success(Unit)
|
||||
}
|
||||
)
|
||||
val migration = createMigration(appPreferencesStore, mediaPreviewService)
|
||||
|
||||
migration().join()
|
||||
|
||||
// Verify server was updated with local value
|
||||
assertThat(setHideInviteAvatarsValue).isTrue()
|
||||
// Verify local data was cleared
|
||||
assertThat(appPreferencesStore.getHideInviteAvatarsFlow().first()).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when local mediaPreviewValue exists and server has no config, migrates to server`() = runTest {
|
||||
val appPreferencesStore = InMemoryAppPreferencesStore().apply {
|
||||
setTimelineMediaPreviewValue(MediaPreviewValue.Private)
|
||||
}
|
||||
var setMediaPreviewValue: MediaPreviewValue? = null
|
||||
val mediaPreviewService = FakeMediaPreviewService(
|
||||
fetchMediaPreviewConfigResult = { Result.success(null) },
|
||||
setMediaPreviewValueResult = { value ->
|
||||
setMediaPreviewValue = value
|
||||
Result.success(Unit)
|
||||
}
|
||||
)
|
||||
val migration = createMigration(appPreferencesStore, mediaPreviewService)
|
||||
|
||||
migration().join()
|
||||
|
||||
// Verify server was updated with local value
|
||||
assertThat(setMediaPreviewValue).isEqualTo(MediaPreviewValue.Private)
|
||||
// Verify local data was cleared
|
||||
assertThat(appPreferencesStore.getTimelineMediaPreviewValueFlow().first()).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when both local values exist and server has no config, migrates both to server`() = runTest {
|
||||
val appPreferencesStore = InMemoryAppPreferencesStore().apply {
|
||||
setHideInviteAvatars(true)
|
||||
setTimelineMediaPreviewValue(MediaPreviewValue.Off)
|
||||
}
|
||||
var setHideInviteAvatarsValue: Boolean? = null
|
||||
var setMediaPreviewValue: MediaPreviewValue? = null
|
||||
val mediaPreviewService = FakeMediaPreviewService(
|
||||
fetchMediaPreviewConfigResult = { Result.success(null) },
|
||||
setHideInviteAvatarsResult = { value ->
|
||||
setHideInviteAvatarsValue = value
|
||||
Result.success(Unit)
|
||||
},
|
||||
setMediaPreviewValueResult = { value ->
|
||||
setMediaPreviewValue = value
|
||||
Result.success(Unit)
|
||||
}
|
||||
)
|
||||
val migration = createMigration(appPreferencesStore, mediaPreviewService)
|
||||
|
||||
migration().join()
|
||||
|
||||
// Verify server was updated with both local values
|
||||
assertThat(setHideInviteAvatarsValue).isTrue()
|
||||
assertThat(setMediaPreviewValue).isEqualTo(MediaPreviewValue.Off)
|
||||
// Verify local data was cleared
|
||||
assertThat(appPreferencesStore.getHideInviteAvatarsFlow().first()).isNull()
|
||||
assertThat(appPreferencesStore.getTimelineMediaPreviewValueFlow().first()).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when fetch config fails, migration does nothing`() = runTest {
|
||||
val appPreferencesStore = InMemoryAppPreferencesStore().apply {
|
||||
setHideInviteAvatars(true)
|
||||
setTimelineMediaPreviewValue(MediaPreviewValue.Private)
|
||||
}
|
||||
val mediaPreviewService = FakeMediaPreviewService(
|
||||
fetchMediaPreviewConfigResult = { Result.failure(Exception("Network error")) }
|
||||
)
|
||||
val migration = createMigration(appPreferencesStore, mediaPreviewService)
|
||||
|
||||
migration().join()
|
||||
|
||||
// Verify local data was not cleared since migration failed
|
||||
assertThat(appPreferencesStore.getHideInviteAvatarsFlow().first()).isTrue()
|
||||
assertThat(appPreferencesStore.getTimelineMediaPreviewValueFlow().first()).isEqualTo(MediaPreviewValue.Private)
|
||||
}
|
||||
|
||||
private fun TestScope.createMigration(
|
||||
appPreferencesStore: InMemoryAppPreferencesStore,
|
||||
mediaPreviewService: FakeMediaPreviewService
|
||||
) = MediaPreviewConfigMigration(
|
||||
mediaPreviewService = mediaPreviewService,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
sessionCoroutineScope = this
|
||||
)
|
||||
}
|
||||
|
|
@ -98,6 +98,10 @@ allprojects {
|
|||
|
||||
// Uncomment to suppress Compose Kotlin compiler compatibility warning
|
||||
// freeCompilerArgs.addAll(listOf("-P", "plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=true"))
|
||||
|
||||
// Fix compilation warning for annotations
|
||||
// See https://youtrack.jetbrains.com/issue/KT-73255/Change-defaulting-rule-for-annotations for more details
|
||||
freeCompilerArgs.add("-Xannotation-default-target=first-only")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 4a07c862a23a9fd1418eabf132cf9d6b25ea4927
|
||||
Subproject commit b7ababb9537da8bec254b8ed00b5a4122e9f3e3b
|
||||
2
fastlane/metadata/android/en-US/changelogs/202507000.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/202507000.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
Main changes in this version: improve accessibility.
|
||||
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_settings_help_us_improve">"Del anonyme brugsdata for at hjælpe os med at identificere problemer."</string>
|
||||
<string name="screen_analytics_settings_read_terms">"Du kan læse alle vores vilkår %1$s."</string>
|
||||
<string name="screen_analytics_settings_read_terms_content_link">"her"</string>
|
||||
<string name="screen_analytics_settings_share_data">"Del analysedata"</string>
|
||||
</resources>
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_settings_help_us_improve">"داده های استفاده ناشناس را به اشتراک بگذارید تا به ما در شناسایی مشکلات کمک کند."</string>
|
||||
<string name="screen_analytics_settings_read_terms">"شما میتوانید تمام شرایط ما را بخوانید%1$s ."</string>
|
||||
<string name="screen_analytics_settings_read_terms_content_link">"اینجا"</string>
|
||||
<string name="screen_analytics_settings_share_data">"هم رسانی دادههای تحلیلی"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_prompt_data_usage">"Vi vil ikke registrere eller profilere nogen personlige data"</string>
|
||||
<string name="screen_analytics_prompt_help_us_improve">"Del anonyme brugsdata for at hjælpe os med at identificere problemer."</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"Du kan læse alle vores vilkår %1$s."</string>
|
||||
<string name="screen_analytics_prompt_read_terms_content_link">"her"</string>
|
||||
<string name="screen_analytics_prompt_settings">"Du kan slå dette fra når som helst"</string>
|
||||
<string name="screen_analytics_prompt_third_party_sharing">"Vi deler ikke dine data med tredjeparter"</string>
|
||||
<string name="screen_analytics_prompt_title">"Hjælp med at forbedre %1$s"</string>
|
||||
</resources>
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_analytics_prompt_data_usage">"ما هیچ گونه اطلاعات شخصی را ضبط یا نمایهسازی نمیکنیم"</string>
|
||||
<string name="screen_analytics_prompt_help_us_improve">"داده های استفاده ناشناس را به اشتراک بگذارید تا به ما در شناسایی مشکلات کمک کند."</string>
|
||||
<string name="screen_analytics_prompt_read_terms">"شما میتوانید تمام شرایط ما را بخوانید%1$s ."</string>
|
||||
<string name="screen_analytics_prompt_read_terms_content_link">"اینجا"</string>
|
||||
<string name="screen_analytics_prompt_settings">"میتوانید در هر زمان خاموشش کنید"</string>
|
||||
<string name="screen_analytics_prompt_third_party_sharing">"دادههایتان را با سومشخصها همنمیرسانیم"</string>
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ data class WidgetMessage(
|
|||
|
||||
@Serializable
|
||||
enum class Action {
|
||||
@SerialName("io.element.join")
|
||||
Join,
|
||||
|
||||
@SerialName("im.vector.hangup")
|
||||
HangUp,
|
||||
|
||||
|
|
|
|||
|
|
@ -7,16 +7,6 @@
|
|||
|
||||
package io.element.android.features.call.impl.pip
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
|
||||
open class PictureInPictureStateProvider : PreviewParameterProvider<PictureInPictureState> {
|
||||
override val values: Sequence<PictureInPictureState>
|
||||
get() = sequenceOf(
|
||||
aPictureInPictureState(supportPip = true),
|
||||
aPictureInPictureState(supportPip = true, isInPictureInPicture = true),
|
||||
)
|
||||
}
|
||||
|
||||
fun aPictureInPictureState(
|
||||
supportPip: Boolean = false,
|
||||
isInPictureInPicture: Boolean = false,
|
||||
|
|
|
|||
|
|
@ -48,9 +48,6 @@ import kotlinx.coroutines.delay
|
|||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.json.contentOrNull
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
|
@ -91,6 +88,7 @@ class CallScreenPresenter @AssistedInject constructor(
|
|||
var webViewError by remember { mutableStateOf<String?>(null) }
|
||||
val languageTag = languageTagProvider.provideLanguageTag()
|
||||
val theme = if (ElementTheme.isLightTheme) "light" else "dark"
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
coroutineScope.launch {
|
||||
// Sets the call as joined
|
||||
|
|
@ -145,17 +143,25 @@ class CallScreenPresenter @AssistedInject constructor(
|
|||
if (parsedMessage?.direction == WidgetMessage.Direction.FromWidget) {
|
||||
if (parsedMessage.action == WidgetMessage.Action.Close) {
|
||||
close(callWidgetDriver.value, navigator)
|
||||
} else if (parsedMessage.action == WidgetMessage.Action.SendEvent) {
|
||||
// This event is received when a member joins the call, the first one will be the current one
|
||||
val type = parsedMessage.data?.jsonObject?.get("type")?.jsonPrimitive?.contentOrNull
|
||||
if (type == "org.matrix.msc3401.call.member") {
|
||||
isJoinedCall = true
|
||||
}
|
||||
} else if (parsedMessage.action == WidgetMessage.Action.Join) {
|
||||
isJoinedCall = true
|
||||
}
|
||||
}
|
||||
}
|
||||
.launchIn(this)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
// Wait for the call to be joined, if it takes too long, we display an error
|
||||
delay(10.seconds)
|
||||
|
||||
if (!isJoinedCall) {
|
||||
Timber.w("The call took too long to be joined. Displaying an error before exiting.")
|
||||
|
||||
// This will display a simple 'Sorry, an error occurred' dialog and force the user to exit the call
|
||||
webViewError = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun handleEvents(event: CallScreenEvents) {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import android.annotation.SuppressLint
|
|||
import android.util.Log
|
||||
import android.view.ViewGroup
|
||||
import android.webkit.ConsoleMessage
|
||||
import android.webkit.JavascriptInterface
|
||||
import android.webkit.PermissionRequest
|
||||
import android.webkit.WebChromeClient
|
||||
import android.webkit.WebView
|
||||
|
|
@ -19,7 +20,6 @@ import androidx.compose.foundation.layout.Box
|
|||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
|
@ -32,11 +32,9 @@ import androidx.compose.ui.platform.LocalInspectionMode
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.call.impl.R
|
||||
import io.element.android.features.call.impl.pip.PictureInPictureEvents
|
||||
import io.element.android.features.call.impl.pip.PictureInPictureState
|
||||
import io.element.android.features.call.impl.pip.PictureInPictureStateProvider
|
||||
import io.element.android.features.call.impl.pip.aPictureInPictureState
|
||||
import io.element.android.features.call.impl.utils.InvalidAudioDeviceReason
|
||||
import io.element.android.features.call.impl.utils.WebViewAudioManager
|
||||
|
|
@ -44,13 +42,11 @@ import io.element.android.features.call.impl.utils.WebViewPipController
|
|||
import io.element.android.features.call.impl.utils.WebViewWidgetMessageInterceptor
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import timber.log.Timber
|
||||
|
||||
|
|
@ -60,7 +56,6 @@ interface CallScreenNavigator {
|
|||
fun close()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun CallScreenView(
|
||||
state: CallScreenState,
|
||||
|
|
@ -78,19 +73,6 @@ internal fun CallScreenView(
|
|||
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
topBar = {
|
||||
if (!pipState.isInPictureInPicture) {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(R.string.element_call)) },
|
||||
navigationIcon = {
|
||||
BackButton(
|
||||
imageVector = if (pipState.supportPip) CompoundIcons.ArrowLeft() else CompoundIcons.Close(),
|
||||
onClick = ::handleBack,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
) { padding ->
|
||||
BackHandler {
|
||||
handleBack()
|
||||
|
|
@ -127,9 +109,11 @@ internal fun CallScreenView(
|
|||
requestPermissions(androidPermissions.toTypedArray(), callback)
|
||||
},
|
||||
onCreateWebView = { webView ->
|
||||
webView.addBackHandler(onBackPressed = ::handleBack)
|
||||
val interceptor = WebViewWidgetMessageInterceptor(
|
||||
webView = webView,
|
||||
onUrlLoaded = { url ->
|
||||
webView.evaluateJavascript("controls.onBackButtonPressed = () => { backHandler.onBackPressed() }", null)
|
||||
if (webViewAudioManager?.isInCallMode?.get() == false) {
|
||||
Timber.d("URL $url is loaded, starting in-call audio mode")
|
||||
webViewAudioManager?.onCallStarted()
|
||||
|
|
@ -282,6 +266,17 @@ private fun WebView.setup(
|
|||
}
|
||||
}
|
||||
|
||||
private fun WebView.addBackHandler(onBackPressed: () -> Unit) {
|
||||
addJavascriptInterface(
|
||||
object {
|
||||
@Suppress("unused")
|
||||
@JavascriptInterface
|
||||
fun onBackPressed() = onBackPressed()
|
||||
},
|
||||
"backHandler"
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun CallScreenViewPreview(
|
||||
|
|
@ -294,18 +289,6 @@ internal fun CallScreenViewPreview(
|
|||
)
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun CallScreenPipViewPreview(
|
||||
@PreviewParameter(PictureInPictureStateProvider::class) state: PictureInPictureState,
|
||||
) = ElementPreview {
|
||||
CallScreenView(
|
||||
state = aCallScreenState(),
|
||||
pipState = state,
|
||||
requestPermissions = { _, _ -> },
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun InvalidAudioDeviceDialogPreview() = ElementPreview {
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import io.element.android.libraries.designsystem.background.OnboardingBackground
|
|||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
|
|
@ -74,7 +75,8 @@ internal fun IncomingCallScreen(
|
|||
name = notificationData.senderName,
|
||||
url = notificationData.avatarUrl,
|
||||
size = AvatarSize.IncomingCall,
|
||||
)
|
||||
),
|
||||
avatarType = AvatarType.User,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Text(
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@
|
|||
<string name="call_foreground_service_channel_title_android">"Probíhající hovor"</string>
|
||||
<string name="call_foreground_service_message_android">"Klepněte pro návrat k hovoru"</string>
|
||||
<string name="call_foreground_service_title_android">"☎️ Probíhá hovor"</string>
|
||||
<string name="call_invalid_audio_device_bluetooth_devices_disabled">"Element Call nepodporuje používání Bluetooth zvukových zařízení v této verzi systému Android. Vyberte jiné zvukové zařízení."</string>
|
||||
<string name="screen_incoming_call_subtitle_android">"Příchozí Element Call"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@
|
|||
<string name="call_foreground_service_channel_title_android">"Galwad cyfredol"</string>
|
||||
<string name="call_foreground_service_message_android">"Tapio i ddychwelyd i\'r alwad"</string>
|
||||
<string name="call_foreground_service_title_android">"☎️ Galwad ar y gweill"</string>
|
||||
<string name="call_invalid_audio_device_bluetooth_devices_disabled">"Nid yw Element Call yn cefnogi defnyddio dyfeisiau sain Bluetooth yn y fersiwn Android hon. Dewiswch ddyfais sain wahanol."</string>
|
||||
<string name="screen_incoming_call_subtitle_android">"Galwad Element"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="call_foreground_service_channel_title_android">"Igangværende opkald"</string>
|
||||
<string name="call_foreground_service_message_android">"Tryk for at vende tilbage til opkaldet"</string>
|
||||
<string name="call_foreground_service_title_android">"☎️ Opkald i gang"</string>
|
||||
<string name="call_invalid_audio_device_bluetooth_devices_disabled">"Element Call understøtter desværre ikke brug af Bluetooth-lydenheder i denne Android-version. Vælg venligst en anden lydenhed."</string>
|
||||
<string name="screen_incoming_call_subtitle_android">"Indgående Element opkald"</string>
|
||||
</resources>
|
||||
|
|
@ -3,5 +3,6 @@
|
|||
<string name="call_foreground_service_channel_title_android">"Laufender Anruf"</string>
|
||||
<string name="call_foreground_service_message_android">"Tippen, um zum Anruf zurückzukehren"</string>
|
||||
<string name="call_foreground_service_title_android">"☎️ Anruf läuft"</string>
|
||||
<string name="call_invalid_audio_device_bluetooth_devices_disabled">"In dieser Android-Version unterstützt Element Call derzeit keine Bluetooth-Audiogeräte. Bitte wählen Sie ein anderes Audiogerät aus."</string>
|
||||
<string name="screen_incoming_call_subtitle_android">"Eingehender Element Call"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@
|
|||
<string name="call_foreground_service_channel_title_android">"Συνεχής κλήση"</string>
|
||||
<string name="call_foreground_service_message_android">"Πάτα για να επιστρέψεις στην κλήση"</string>
|
||||
<string name="call_foreground_service_title_android">"☎️ Κλήση σε εξέλιξη"</string>
|
||||
<string name="call_invalid_audio_device_bluetooth_devices_disabled">"Το Element Call δεν υποστηρίζει τη χρήση συσκευών ήχου Bluetooth σε αυτήν την έκδοση Android. Επέλεξε μια διαφορετική συσκευή ήχου."</string>
|
||||
<string name="screen_incoming_call_subtitle_android">"Εισερχόμενη κλήση Element"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@
|
|||
<string name="call_foreground_service_channel_title_android">"Käimasolev kõne"</string>
|
||||
<string name="call_foreground_service_message_android">"Kõne juurde naasmiseks klõpsa"</string>
|
||||
<string name="call_foreground_service_title_android">"☎️ Kõne on pooleli"</string>
|
||||
<string name="call_invalid_audio_device_bluetooth_devices_disabled">"Element Call ei võimalda selles Androidi versioonis Bluetoothi heliseadmete kasutamist. Palun vali mõni muu heliseade."</string>
|
||||
<string name="screen_incoming_call_subtitle_android">"Sissetulev Element Calli kõne"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@
|
|||
<string name="call_foreground_service_channel_title_android">"Folyamatban lévő hívás"</string>
|
||||
<string name="call_foreground_service_message_android">"Koppintson a híváshoz való visszatéréshez"</string>
|
||||
<string name="call_foreground_service_title_android">"☎️ Hívás folyamatban"</string>
|
||||
<string name="call_invalid_audio_device_bluetooth_devices_disabled">"Az Element Call nem támogatja a Bluetooth hangeszközök használatát ebben az Android-verzióban. Válasszon másik hangeszközt."</string>
|
||||
<string name="screen_incoming_call_subtitle_android">"Bejövő Element hívás"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@
|
|||
<string name="call_foreground_service_channel_title_android">"Panggilan berlangsung"</string>
|
||||
<string name="call_foreground_service_message_android">"Ketuk untuk kembali ke panggilan"</string>
|
||||
<string name="call_foreground_service_title_android">"☎️ Panggilan sedang berlangsung"</string>
|
||||
<string name="call_invalid_audio_device_bluetooth_devices_disabled">"Element Call tidak mendukung penggunaan perangkat audio Bluetooth di versi Android ini. Silakan pilih perangkat audio yang berbeda."</string>
|
||||
<string name="screen_incoming_call_subtitle_android">"Element Call Masuk"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@
|
|||
<string name="call_foreground_service_channel_title_android">"Chiamata in corso"</string>
|
||||
<string name="call_foreground_service_message_android">"Tocca per tornare alla chiamata"</string>
|
||||
<string name="call_foreground_service_title_android">"☎️ Chiamata in corso"</string>
|
||||
<string name="call_invalid_audio_device_bluetooth_devices_disabled">"Element Call non supporta l\'uso di dispositivi audio Bluetooth in questa versione di Android. Seleziona un dispositivo audio diverso."</string>
|
||||
<string name="screen_incoming_call_subtitle_android">"Chiamata Element Call in arrivo"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@
|
|||
<string name="call_foreground_service_channel_title_android">"Chamada em andamento"</string>
|
||||
<string name="call_foreground_service_message_android">"Toque para retornar à chamada"</string>
|
||||
<string name="call_foreground_service_title_android">"☎️ Chamada em andamento"</string>
|
||||
<string name="call_invalid_audio_device_bluetooth_devices_disabled">"O Element Call não tem suporte a dispositivos de áudio Bluetooth nesta versão do Android. Por favor, selecione um dispositivo de áudio diferente."</string>
|
||||
<string name="screen_incoming_call_subtitle_android">"Chamada do Element recebida"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@
|
|||
<string name="call_foreground_service_channel_title_android">"Chamada em curso"</string>
|
||||
<string name="call_foreground_service_message_android">"Toca para voltar à chamada"</string>
|
||||
<string name="call_foreground_service_title_android">"☎️ Chamada em curso"</string>
|
||||
<string name="call_invalid_audio_device_bluetooth_devices_disabled">"As chamadas do Element não permitem o uso de dispositivos de áudio Bluetooth nesta versão do Android. Por favor, seleciona outro dispositivo."</string>
|
||||
<string name="screen_incoming_call_subtitle_android">"A receber chamada da Element"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@
|
|||
<string name="call_foreground_service_channel_title_android">"Prebiehajúci hovor"</string>
|
||||
<string name="call_foreground_service_message_android">"Ťuknutím sa vrátite k hovoru"</string>
|
||||
<string name="call_foreground_service_title_android">"☎️ Prebieha hovor"</string>
|
||||
<string name="call_invalid_audio_device_bluetooth_devices_disabled">"Element Call nepodporuje používanie zvukových zariadení Bluetooth v tejto verzii systému Android. Vyberte iné zvukové zariadenie."</string>
|
||||
<string name="screen_incoming_call_subtitle_android">"Prichádzajúci hovor Element Call"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@
|
|||
<string name="call_foreground_service_channel_title_android">"Поточний виклик"</string>
|
||||
<string name="call_foreground_service_message_android">"Торкніться, щоб повернутися до виклику"</string>
|
||||
<string name="call_foreground_service_title_android">"☎️ Триває виклик"</string>
|
||||
<string name="call_invalid_audio_device_bluetooth_devices_disabled">"Element Call не підтримує використання аудіопристроїв Bluetooth у цій версії Android. Виберіть інший аудіопристрій."</string>
|
||||
<string name="screen_incoming_call_subtitle_android">"Вхідний виклик Element"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1,83 +0,0 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.call.impl.ui
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.features.call.impl.pip.PictureInPictureEvents
|
||||
import io.element.android.features.call.impl.pip.PictureInPictureState
|
||||
import io.element.android.features.call.impl.pip.aPictureInPictureState
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.pressBack
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class CallScreenViewTest {
|
||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
@Test
|
||||
fun `clicking on back when pip is not supported hangs up`() {
|
||||
val eventsRecorder = EventsRecorder<CallScreenEvents>()
|
||||
val pipEventsRecorder = EventsRecorder<PictureInPictureEvents>()
|
||||
rule.setCallScreenView(
|
||||
aCallScreenState(
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
aPictureInPictureState(
|
||||
supportPip = false,
|
||||
eventSink = pipEventsRecorder,
|
||||
),
|
||||
)
|
||||
rule.pressBack()
|
||||
eventsRecorder.assertSize(2)
|
||||
eventsRecorder.assertTrue(0) { it is CallScreenEvents.SetupMessageChannels }
|
||||
eventsRecorder.assertTrue(1) { it == CallScreenEvents.Hangup }
|
||||
pipEventsRecorder.assertSize(1)
|
||||
pipEventsRecorder.assertTrue(0) { it is PictureInPictureEvents.SetPipController }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on back when pip is supported enables PiP`() {
|
||||
val eventsRecorder = EventsRecorder<CallScreenEvents>()
|
||||
val pipEventsRecorder = EventsRecorder<PictureInPictureEvents>()
|
||||
rule.setCallScreenView(
|
||||
aCallScreenState(
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
aPictureInPictureState(
|
||||
supportPip = true,
|
||||
eventSink = pipEventsRecorder,
|
||||
),
|
||||
)
|
||||
rule.pressBack()
|
||||
eventsRecorder.assertSize(1)
|
||||
eventsRecorder.assertTrue(0) { it is CallScreenEvents.SetupMessageChannels }
|
||||
pipEventsRecorder.assertSize(2)
|
||||
pipEventsRecorder.assertTrue(0) { it is PictureInPictureEvents.SetPipController }
|
||||
pipEventsRecorder.assertTrue(1) { it == PictureInPictureEvents.EnterPictureInPicture }
|
||||
}
|
||||
}
|
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setCallScreenView(
|
||||
state: CallScreenState,
|
||||
pipState: PictureInPictureState,
|
||||
requestPermissions: (Array<String>, RequestPermissionCallback) -> Unit = { _, _ -> },
|
||||
) {
|
||||
setContent {
|
||||
CallScreenView(
|
||||
state = state,
|
||||
pipState = pipState,
|
||||
requestPermissions = requestPermissions,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -225,7 +225,7 @@ import kotlin.time.Duration.Companion.seconds
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `present - a received room member message makes the call to be active`() = runTest {
|
||||
fun `present - a received 'joined' action makes the call to be active`() = runTest {
|
||||
val navigator = FakeCallScreenNavigator()
|
||||
val widgetDriver = FakeMatrixWidgetDriver()
|
||||
val presenter = createCallScreenPresenter(
|
||||
|
|
@ -248,13 +248,10 @@ import kotlin.time.Duration.Companion.seconds
|
|||
messageInterceptor.givenInterceptedMessage(
|
||||
"""
|
||||
{
|
||||
"action":"send_event",
|
||||
"action":"io.element.join",
|
||||
"api":"fromWidget",
|
||||
"widgetId":"1",
|
||||
"requestId":"1",
|
||||
"data":{
|
||||
"type":"org.matrix.msc3401.call.member"
|
||||
}
|
||||
"requestId":"1"
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
|
@ -264,6 +261,40 @@ import kotlin.time.Duration.Companion.seconds
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - if in room mode and no join action is received an error is displayed`() = runTest {
|
||||
val navigator = FakeCallScreenNavigator()
|
||||
val widgetDriver = FakeMatrixWidgetDriver()
|
||||
val presenter = createCallScreenPresenter(
|
||||
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID),
|
||||
widgetDriver = widgetDriver,
|
||||
navigator = navigator,
|
||||
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
||||
screenTracker = FakeScreenTracker {},
|
||||
)
|
||||
val messageInterceptor = FakeWidgetMessageInterceptor()
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
// Give it time to load the URL and WidgetDriver
|
||||
advanceTimeBy(1.seconds)
|
||||
skipItems(2)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.isCallActive).isFalse()
|
||||
initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor))
|
||||
skipItems(2)
|
||||
|
||||
// Wait for the timeout to trigger
|
||||
advanceTimeBy(10.seconds)
|
||||
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.isCallActive).isFalse()
|
||||
// The error dialog that will force the user to leave the call is displayed
|
||||
assertThat(finalState.webViewError).isNotNull()
|
||||
assertThat(finalState.webViewError).isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - automatically sets the isInCall state when starting the call and disposing the screen`() = runTest {
|
||||
val navigator = FakeCallScreenNavigator()
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.createroom.impl.R
|
||||
import io.element.android.features.createroom.impl.components.UserListView
|
||||
import io.element.android.features.createroom.impl.userlist.UserListEvents
|
||||
|
|
@ -23,9 +22,7 @@ import io.element.android.features.createroom.impl.userlist.UserListState
|
|||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
|
@ -74,12 +71,7 @@ private fun AddPeopleViewTopBar(
|
|||
onNextClick: () -> Unit,
|
||||
) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.screen_create_room_add_people_title),
|
||||
style = ElementTheme.typography.aliasScreenTitle
|
||||
)
|
||||
},
|
||||
titleStr = stringResource(id = R.string.screen_create_room_add_people_title),
|
||||
navigationIcon = { BackButton(onClick = onBackClick) },
|
||||
actions = {
|
||||
val textActionResId = if (hasSelectedUsers) CommonStrings.action_next else CommonStrings.action_skip
|
||||
|
|
|
|||
|
|
@ -40,13 +40,14 @@ import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtom
|
|||
import io.element.android.libraries.designsystem.atomic.atoms.RoundedIconAtomSize
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncActionViewDefaults
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||
import io.element.android.libraries.designsystem.modifiers.clearFocusOnTap
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||
import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight
|
||||
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
|
||||
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
|
|
@ -187,12 +188,7 @@ private fun ConfigureRoomToolbar(
|
|||
onNextClick: () -> Unit,
|
||||
) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.screen_create_room_title),
|
||||
style = ElementTheme.typography.aliasScreenTitle,
|
||||
)
|
||||
},
|
||||
titleStr = stringResource(R.string.screen_create_room_title),
|
||||
navigationIcon = { BackButton(onClick = onBackClick) },
|
||||
actions = {
|
||||
TextButton(
|
||||
|
|
@ -219,6 +215,8 @@ private fun RoomNameWithAvatar(
|
|||
) {
|
||||
UnsavedAvatar(
|
||||
avatarUri = avatarUri,
|
||||
avatarSize = AvatarSize.EditRoomDetails,
|
||||
avatarType = AvatarType.Room(),
|
||||
modifier = Modifier.clickable(onClick = onAvatarClick),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ import io.element.android.libraries.designsystem.components.button.BackButton
|
|||
import io.element.android.libraries.designsystem.icons.CompoundDrawables
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.ListSectionHeader
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
|
|
@ -137,12 +136,7 @@ private fun CreateRoomRootViewTopBar(
|
|||
onCloseClick: () -> Unit,
|
||||
) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(id = CommonStrings.action_start_chat),
|
||||
style = ElementTheme.typography.aliasScreenTitle,
|
||||
)
|
||||
},
|
||||
titleStr = stringResource(id = CommonStrings.action_start_chat),
|
||||
navigationIcon = {
|
||||
BackButton(
|
||||
imageVector = CompoundIcons.Close(),
|
||||
|
|
|
|||
|
|
@ -3,10 +3,21 @@
|
|||
<string name="screen_create_room_action_create_room">"Нова стая"</string>
|
||||
<string name="screen_create_room_add_people_title">"Поканване на хора"</string>
|
||||
<string name="screen_create_room_error_creating_room">"Възникна грешка при създаването на стаята"</string>
|
||||
<string name="screen_create_room_private_option_description">"Съобщенията в тази стая са шифровани. Шифроването не може да бъде изключено впоследствие."</string>
|
||||
<string name="screen_create_room_private_option_title">"Частна стая (само с покана)"</string>
|
||||
<string name="screen_create_room_public_option_description">"Съобщенията не са шифровани и всеки може да ги прочете. Можете да активирате шифроването на по-късна дата."</string>
|
||||
<string name="screen_create_room_private_option_description">"Само поканени хора имат достъп до тази стая. Всички съобщения са шифровани от край до край."</string>
|
||||
<string name="screen_create_room_private_option_title">"Частна стая"</string>
|
||||
<string name="screen_create_room_public_option_description">"Всеки може да намери тази стая.
|
||||
Можете да промените това по всяко време в настройките на стаята."</string>
|
||||
<string name="screen_create_room_public_option_title">"Общодостъпна стая"</string>
|
||||
<string name="screen_create_room_room_access_section_anyone_option_description">"Всеки може да се присъедини към тази стая"</string>
|
||||
<string name="screen_create_room_room_access_section_anyone_option_title">"Всеки"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"За да бъде тази стая видима в директорията на общодостъпните стаи, ще ви е необходим адрес на стаята."</string>
|
||||
<string name="screen_create_room_room_name_label">"Име на стаята"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Видимост на стаята"</string>
|
||||
<string name="screen_create_room_title">"Създаване на стая"</string>
|
||||
<string name="screen_create_room_topic_label">"Тема за разговор (незадължително)"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_action">"Присъединяване към стая по адрес"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Не е валиден адрес"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Въведете…"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Стаята не е намерена"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"напр. #room-name:matrix.org"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_create_room_action_create_room">"Nyt rum"</string>
|
||||
<string name="screen_create_room_add_people_title">"Invitér folk"</string>
|
||||
<string name="screen_create_room_error_creating_room">"Der opstod en fejl ved oprettelsen af rummet"</string>
|
||||
<string name="screen_create_room_private_option_description">"Kun inviterede personer kan få adgang til dette rum. Alle meddelelser er ende-til-ende krypteret."</string>
|
||||
<string name="screen_create_room_private_option_title">"Privat rum"</string>
|
||||
<string name="screen_create_room_public_option_description">"Alle kan finde dette rum.
|
||||
Du kan ændre dette når som helst i rummets indstillinger."</string>
|
||||
<string name="screen_create_room_public_option_title">"Offentligt rum"</string>
|
||||
<string name="screen_create_room_room_access_section_anyone_option_description">"Alle kan deltage i dette rum"</string>
|
||||
<string name="screen_create_room_room_access_section_anyone_option_title">"Enhver"</string>
|
||||
<string name="screen_create_room_room_access_section_header">"Adgang til rummet"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Alle kan bede om at deltage i rummet, men en administrator eller en moderator skal acceptere anmodningen"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Spørg om at deltage"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Hvis dette rum skal være synligt i det offentlige register, skal du bruge en rum-adresse."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Rummets adresse"</string>
|
||||
<string name="screen_create_room_room_name_label">"Navn på rum"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Rummets synlighed"</string>
|
||||
<string name="screen_create_room_title">"Opret et rum"</string>
|
||||
<string name="screen_create_room_topic_label">"Emne (valgfrit)"</string>
|
||||
<string name="screen_room_directory_search_title">"Register over rum"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Der opstod en fejl under forsøget på at starte en samtale"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_action">"Tilslut dig rummet med adressen"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Ikke en gyldig adresse"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Indtast…"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_room_found">"Matchende rum fundet"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Rum ikke fundet"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"f.eks. #rummets-navn:matrix.org"</string>
|
||||
</resources>
|
||||
|
|
@ -1,30 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_create_room_action_create_room">"Νέο δωμάτιο"</string>
|
||||
<string name="screen_create_room_action_create_room">"Νέα αίθουσα"</string>
|
||||
<string name="screen_create_room_add_people_title">"Πρόσκληση ατόμων"</string>
|
||||
<string name="screen_create_room_error_creating_room">"Παρουσιάστηκε σφάλμα κατά τη δημιουργία του δωματίου"</string>
|
||||
<string name="screen_create_room_private_option_description">"Μόνο άτομα που έχουν προσκληθεί μπορούν να έχουν πρόσβαση σε αυτό το δωμάτιο. Όλα τα μηνύματα είναι κρυπτογραφημένα από άκρο σε άκρο."</string>
|
||||
<string name="screen_create_room_private_option_title">"Ιδιωτικό δωμάτιο"</string>
|
||||
<string name="screen_create_room_public_option_description">"Ο καθένας μπορεί να βρει αυτό το δωμάτιο.
|
||||
Μπορείς να το αλλάξεις ανά πάσα στιγμή στις ρυθμίσεις δωματίου."</string>
|
||||
<string name="screen_create_room_public_option_title">"Δημόσιο δωμάτιο"</string>
|
||||
<string name="screen_create_room_room_access_section_anyone_option_description">"Οποιοσδήποτε μπορεί να συμμετάσχει σε αυτό το δωμάτιο"</string>
|
||||
<string name="screen_create_room_error_creating_room">"Προέκυψε σφάλμα κατά τη δημιουργία της αίθουσας"</string>
|
||||
<string name="screen_create_room_private_option_description">"Μόνο τα άτομα που έχουν προσκληθεί μπορούν να έχουν πρόσβαση σε αυτή την αίθουσα. Όλα τα μηνύματα είναι κρυπτογραφημένα από άκρο σε άκρο."</string>
|
||||
<string name="screen_create_room_private_option_title">"Ιδιωτική αίθουσα"</string>
|
||||
<string name="screen_create_room_public_option_description">"Ο καθένας μπορεί να βρει αυτή την αίθουσα.
|
||||
Αυτό μπορείτε να το αλλάξετε ανά πάσα στιγμή στις ρυθμίσεις της αίθουσας."</string>
|
||||
<string name="screen_create_room_public_option_title">"Δημόσια αίθουσα"</string>
|
||||
<string name="screen_create_room_room_access_section_anyone_option_description">"Οποιοσδήποτε μπορεί να συμμετάσχει σε αυτή την αίθουσα"</string>
|
||||
<string name="screen_create_room_room_access_section_anyone_option_title">"Οποιοσδήποτε"</string>
|
||||
<string name="screen_create_room_room_access_section_header">"Πρόσβαση Δωματίου"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Οποιοσδήποτε μπορεί να ζητήσει να συμμετάσχει στο δωμάτιο, αλλά ένας διαχειριστής ή συντονιστής θα πρέπει να αποδεχθεί το αίτημα"</string>
|
||||
<string name="screen_create_room_room_access_section_header">"Πρόσβαση στην Αίθουσα"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_description">"Οποιοσδήποτε μπορεί να ζητήσει να συμμετάσχει στην αίθουσα, αλλά ένας διαχειριστής ή ένας συντονιστής θα πρέπει να αποδεχτεί το αίτημα"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Αίτημα συμμετοχής"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Για να είναι ορατό αυτό το δωμάτιο στον κατάλογο των δημόσιων δωματίων, θα χρειαστείς μια διεύθυνση δωματίου."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Διεύθυνση δωματίου"</string>
|
||||
<string name="screen_create_room_room_name_label">"Όνομα δωματίου"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Ορατότητα δωματίου"</string>
|
||||
<string name="screen_create_room_title">"Δημιούργησε ένα δωμάτιο"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Για να είναι ορατή αυτή η αίθουσα στον δημόσιο κατάλογο αιθουσών, θα χρειαστείτε μια διεύθυνση αίθουσας."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Διεύθυνση αίθουσας"</string>
|
||||
<string name="screen_create_room_room_name_label">"Όνομα αίθουσας"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Ορατότητα αίθουσας"</string>
|
||||
<string name="screen_create_room_title">"Δημιουργία αίθουσας"</string>
|
||||
<string name="screen_create_room_topic_label">"Θέμα (προαιρετικό)"</string>
|
||||
<string name="screen_room_directory_search_title">"Κατάλογος δωματίων"</string>
|
||||
<string name="screen_room_directory_search_title">"Κατάλογος αιθουσών"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Παρουσιάστηκε σφάλμα κατά την προσπάθεια έναρξης μιας συνομιλίας"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_action">"Συμμετοχή σε δωμάτιο μέσω διεύθυνσης"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_action">"Συμμετοχή σε αίθουσα μέσω διεύθυνσης"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Μη έγκυρη διεύθυνση"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Εισάγετε…"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_room_found">"Βρέθηκε το αντίστοιχο δωμάτιο"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Το δωμάτιο δε βρέθηκε"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"π.χ. #όνομα-δωματίου:matrix.org"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_room_found">"Βρέθηκε η αντίστοιχη αίθουσα"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Η αίθουσα δεν βρέθηκε"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"π.χ. #όνομα-αίθουσας:matrix.org"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
<string name="screen_create_room_title">"ایجاد اتاق"</string>
|
||||
<string name="screen_create_room_topic_label">"موضوع (اختیاری)"</string>
|
||||
<string name="screen_room_directory_search_title">"فهرست اتاقها"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"هنگام تلاش برای شروع چت خطایی روی داد"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_action">"پیوستن به اتاق با نشانی"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"نشانی معتبری نیست"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_placeholder">"ورود…"</string>
|
||||
|
|
|
|||
|
|
@ -14,10 +14,17 @@ Anda dapat mengubah ini kapan pun dalam pengaturan ruangan."</string>
|
|||
<string name="screen_create_room_room_access_section_knocking_option_description">"Siapa pun dapat meminta untuk bergabung dengan ruangan tetapi administrator atau moderator harus menerima permintaan tersebut"</string>
|
||||
<string name="screen_create_room_room_access_section_knocking_option_title">"Minta untuk bergabung"</string>
|
||||
<string name="screen_create_room_room_address_section_footer">"Supaya ruangan ini terlihat di direktori ruangan publik, Anda memerlukan alamat ruangan."</string>
|
||||
<string name="screen_create_room_room_address_section_title">"Alamat ruangan"</string>
|
||||
<string name="screen_create_room_room_name_label">"Nama ruangan"</string>
|
||||
<string name="screen_create_room_room_visibility_section_title">"Keterlihatan ruangan"</string>
|
||||
<string name="screen_create_room_title">"Buat ruangan"</string>
|
||||
<string name="screen_create_room_topic_label">"Topik (opsional)"</string>
|
||||
<string name="screen_room_directory_search_title">"Direktori ruangan"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Terjadi kesalahan saat mencoba memulai obrolan"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_action">"Bergabung dalam ruangan berdasarkan alamat"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_invalid_address">"Bukan alamat yang valid"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_placeholder">"Masuk…"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_room_found">"Ruangan yang cocok ditemukan"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_room_not_found">"Ruangan tidak ditemukan"</string>
|
||||
<string name="screen_start_chat_join_room_by_address_supporting_text">"mis. #nama-ruangan:matrix.org"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -57,7 +57,6 @@ import io.element.android.libraries.designsystem.modifiers.onTabOrEnterKeyFocusN
|
|||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.text.buildAnnotatedStringWithStyledPart
|
||||
import io.element.android.libraries.designsystem.theme.aliasScreenTitle
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
|
|
@ -85,12 +84,7 @@ fun AccountDeactivationView(
|
|||
navigationIcon = {
|
||||
BackButton(onClick = onBackClick)
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(R.string.screen_deactivate_account_title),
|
||||
style = ElementTheme.typography.aliasScreenTitle,
|
||||
)
|
||||
},
|
||||
titleStr = stringResource(R.string.screen_deactivate_account_title),
|
||||
)
|
||||
},
|
||||
) { padding ->
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_deactivate_account_confirmation_dialog_content">"Моля, потвърдете, че искате да деактивирате акаунта си. Това действие не може да бъде отменено."</string>
|
||||
<string name="screen_deactivate_account_title">"Деактивиране на акаунта"</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_deactivate_account_confirmation_dialog_content">"Bekræft venligst, at du vil deaktivere din konto. Denne handling kan ikke fortrydes."</string>
|
||||
<string name="screen_deactivate_account_delete_all_messages">"Slet alle mine beskeder"</string>
|
||||
<string name="screen_deactivate_account_delete_all_messages_notice">"Advarsel: Fremtidige brugere kan muligvis se ufuldstændige samtaler."</string>
|
||||
<string name="screen_deactivate_account_description">"Deaktivering af din konto er %1$s, det vil:"</string>
|
||||
<string name="screen_deactivate_account_description_bold_part">"irreversibel"</string>
|
||||
<string name="screen_deactivate_account_list_item_1">"%1$s din konto (du kan ikke logge ind igen, og dit ID kan ikke genbruges)."</string>
|
||||
<string name="screen_deactivate_account_list_item_1_bold_part">"Deaktiver permanent"</string>
|
||||
<string name="screen_deactivate_account_list_item_2">"Fjern dig fra alle samtalerum"</string>
|
||||
<string name="screen_deactivate_account_list_item_3">"Slette dine kontooplysninger fra vores identitetsserver."</string>
|
||||
<string name="screen_deactivate_account_list_item_4">"Dine beskeder vil stadig være synlige for registrerede brugere, men vil ikke være tilgængelige for nye eller uregistrerede brugere, hvis du vælger at slette dem."</string>
|
||||
<string name="screen_deactivate_account_title">"Deaktiver konto"</string>
|
||||
</resources>
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
<string name="screen_deactivate_account_description_bold_part">"μη αναστρέψιμο"</string>
|
||||
<string name="screen_deactivate_account_list_item_1">"%1$s τον λογαριασμό σου (δεν μπορείς να συνδεθείς ξανά και το αναγνωριστικό σου δεν μπορεί να επαναχρησιμοποιηθεί)."</string>
|
||||
<string name="screen_deactivate_account_list_item_1_bold_part">"Μόνιμη απενεργοποίηση"</string>
|
||||
<string name="screen_deactivate_account_list_item_2">"Σε αφαιρέσει από όλα τα δωμάτια συνομιλίας."</string>
|
||||
<string name="screen_deactivate_account_list_item_2">"Αποχώρησή σας από όλες τις αίθουσες συνομιλίας."</string>
|
||||
<string name="screen_deactivate_account_list_item_3">"Διαγράψει τα στοιχεία του λογαριασμού σου από τον διακομιστή ταυτότητάς μας."</string>
|
||||
<string name="screen_deactivate_account_list_item_4">"Τα μηνύματά σου θα εξακολουθούν να είναι ορατά στους εγγεγραμμένους χρήστες, αλλά δεν θα είναι διαθέσιμα σε νέους ή μη εγγεγραμμένους χρήστες εάν επιλέξεις να τα διαγράψεις."</string>
|
||||
<string name="screen_deactivate_account_title">"Απενεργοποίηση λογαριασμού"</string>
|
||||
|
|
|
|||
|
|
@ -16,8 +16,6 @@ interface EnterpriseService {
|
|||
fun defaultHomeserverList(): List<String>
|
||||
suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String): Boolean
|
||||
|
||||
suspend fun isElementCallAvailable(): Boolean
|
||||
|
||||
fun semanticColorsLight(): SemanticColors
|
||||
fun semanticColorsDark(): SemanticColors
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.enterprise.api
|
||||
|
||||
interface SessionEnterpriseService {
|
||||
suspend fun isElementCallAvailable(): Boolean
|
||||
}
|
||||
|
|
@ -25,8 +25,6 @@ class DefaultEnterpriseService @Inject constructor() : EnterpriseService {
|
|||
override fun defaultHomeserverList(): List<String> = emptyList()
|
||||
override suspend fun isAllowedToConnectToHomeserver(homeserverUrl: String) = true
|
||||
|
||||
override suspend fun isElementCallAvailable(): Boolean = true
|
||||
|
||||
override fun semanticColorsLight(): SemanticColors = compoundColorsLight
|
||||
|
||||
override fun semanticColorsDark(): SemanticColors = compoundColorsDark
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.enterprise.impl
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.enterprise.api.SessionEnterpriseService
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(SessionScope::class)
|
||||
class DefaultSessionEnterpriseService @Inject constructor() : SessionEnterpriseService {
|
||||
override suspend fun isElementCallAvailable(): Boolean = true
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.enterprise.impl
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultSessionEnterpriseServiceTest {
|
||||
@Test
|
||||
fun `isElementCallAvailable is always true`() = runTest {
|
||||
val service = DefaultSessionEnterpriseService()
|
||||
assertThat(service.isElementCallAvailable()).isTrue()
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,6 @@ class FakeEnterpriseService(
|
|||
private val isEnterpriseUserResult: (SessionId) -> Boolean = { lambdaError() },
|
||||
private val defaultHomeserverListResult: () -> List<String> = { emptyList() },
|
||||
private val isAllowedToConnectToHomeserverResult: (String) -> Boolean = { lambdaError() },
|
||||
private val isElementCallAvailableResult: () -> Boolean = { lambdaError() },
|
||||
private val semanticColorsLightResult: () -> SemanticColors = { lambdaError() },
|
||||
private val semanticColorsDarkResult: () -> SemanticColors = { lambdaError() },
|
||||
private val firebasePushGatewayResult: () -> String? = { lambdaError() },
|
||||
|
|
@ -36,10 +35,6 @@ class FakeEnterpriseService(
|
|||
isAllowedToConnectToHomeserverResult(homeserverUrl)
|
||||
}
|
||||
|
||||
override suspend fun isElementCallAvailable(): Boolean = simulateLongTask {
|
||||
isElementCallAvailableResult()
|
||||
}
|
||||
|
||||
override fun semanticColorsLight(): SemanticColors {
|
||||
return semanticColorsLightResult()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.enterprise.test
|
||||
|
||||
import io.element.android.features.enterprise.api.SessionEnterpriseService
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import io.element.android.tests.testutils.simulateLongTask
|
||||
|
||||
class FakeSessionEnterpriseService(
|
||||
private val isElementCallAvailableResult: () -> Boolean = { lambdaError() },
|
||||
) : SessionEnterpriseService {
|
||||
override suspend fun isElementCallAvailable(): Boolean = simulateLongTask {
|
||||
isElementCallAvailableResult()
|
||||
}
|
||||
}
|
||||
|
|
@ -38,6 +38,7 @@ import io.element.android.libraries.designsystem.components.PageTitle
|
|||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
|
|
@ -149,6 +150,7 @@ private fun NotificationRow(
|
|||
) {
|
||||
Avatar(
|
||||
avatarData = AvatarData(id = avatarColorsId, name = avatarLetter, size = AvatarSize.NotificationsOptIn),
|
||||
avatarType = AvatarType.User,
|
||||
)
|
||||
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Box(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Не можете да потвърдите?"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Потвърдете това устройство, за да настроите защитени съобщения."</string>
|
||||
<string name="screen_identity_confirmation_title">"Потвърдете самоличността си"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Използване на друго устройство"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Използване на ключ за възстановяване"</string>
|
||||
<string name="screen_identity_confirmed_title">"Устройството е потвърдено"</string>
|
||||
<string name="screen_identity_use_another_device">"Използване на друго устройство"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Можете да промените настройките си по-късно."</string>
|
||||
<string name="screen_notification_optin_title">"Разрешете известията и никога не пропускайте съобщение"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Въвеждане на ключ за възстановяване"</string>
|
||||
|
|
|
|||
22
features/ftue/impl/src/main/res/values-da/translations.xml
Normal file
22
features/ftue/impl/src/main/res/values-da/translations.xml
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmation_cannot_confirm">"Kan ikke bekræfte?"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"Opret en ny gendannelsesnøgle"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"Verificér denne enhed for at konfigurere sikre meddelelser."</string>
|
||||
<string name="screen_identity_confirmation_title">"Bekræft din identitet"</string>
|
||||
<string name="screen_identity_confirmation_use_another_device">"Brug en anden enhed"</string>
|
||||
<string name="screen_identity_confirmation_use_recovery_key">"Brug gendannelsesnøgle"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"Nu kan du læse eller sende beskeder sikkert, og enhver du samtaler med kan også stole på denne enhed."</string>
|
||||
<string name="screen_identity_confirmed_title">"Enhed verificeret"</string>
|
||||
<string name="screen_identity_use_another_device">"Brug en anden enhed"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"Venter på en anden enhed…"</string>
|
||||
<string name="screen_notification_optin_subtitle">"Du kan ændre dine indstillinger senere."</string>
|
||||
<string name="screen_notification_optin_title">"Tillad notifikationer, og gå aldrig glip af en besked"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Indtast gendannelsesnøgle"</string>
|
||||
<string name="screen_welcome_bullet_1">"Opkald, afstemninger, søgninger og mere vil blive tilføjet senere på året."</string>
|
||||
<string name="screen_welcome_bullet_2">"Beskedhistorik for krypterede rum er ikke tilgængelig endnu."</string>
|
||||
<string name="screen_welcome_bullet_3">"Vi vil meget gerne høre fra dig. Fortæl os din mening via indstillingssiden."</string>
|
||||
<string name="screen_welcome_button">"Lad os komme i gang!"</string>
|
||||
<string name="screen_welcome_subtitle">"Her er, hvad du har brug for at vide:"</string>
|
||||
<string name="screen_welcome_title">"Velkommen til %1$s!"</string>
|
||||
</resources>
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
<string name="screen_notification_optin_title">"Επέτρεψε τις ειδοποιήσεις και μην χάσεις ούτε ένα μήνυμα"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"Εισαγωγή κλειδιού ανάκτησης"</string>
|
||||
<string name="screen_welcome_bullet_1">"Κλήσεις, δημοσκοπήσεις, αναζήτηση και άλλα, θα προστεθούν αργότερα φέτος."</string>
|
||||
<string name="screen_welcome_bullet_2">"Το ιστορικό μηνυμάτων για κρυπτογραφημένα δωμάτια δεν είναι ακόμα διαθέσιμο."</string>
|
||||
<string name="screen_welcome_bullet_2">"Το ιστορικό μηνυμάτων για κρυπτογραφημένες αίθουσες δεν είναι ακόμη διαθέσιμο."</string>
|
||||
<string name="screen_welcome_bullet_3">"Θα θέλαμε να ακούσουμε τη γνώμη σου, πες μας τη γνώμη σου μέσω της σελίδας ρυθμίσεων."</string>
|
||||
<string name="screen_welcome_button">"Πάμε!"</string>
|
||||
<string name="screen_welcome_subtitle">"Να τί πρέπει να ξέρεις:"</string>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@
|
|||
<string name="screen_notification_optin_subtitle">"میتوانید بعداً تنظیماتتان را تغییر دهید."</string>
|
||||
<string name="screen_notification_optin_title">"اجازه به آگاهیها و از دست ندادن پیامها"</string>
|
||||
<string name="screen_session_verification_enter_recovery_key">"ورود کلید بازیابی"</string>
|
||||
<string name="screen_welcome_bullet_1">"تماس ها، نظرسنجی، جستجو و موارد دیگر در اواخر امسال اضافه خواهند شد."</string>
|
||||
<string name="screen_welcome_bullet_2">"سابقه پیام برای اتاق های رمزگذاری شده هنوز دردسترس نیست."</string>
|
||||
<string name="screen_welcome_bullet_3">"ما دوست داریم از شما بشنویم، نظر خود را از طریق صفحه تنظیمات با ما در میان بگذارید."</string>
|
||||
<string name="screen_welcome_button">"بزن بریم!"</string>
|
||||
<string name="screen_welcome_subtitle">"چیزهایی که باید بدانید:"</string>
|
||||
<string name="screen_welcome_title">"به %1$s خوش آمدید!"</string>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ plugins {
|
|||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.roomlist.api"
|
||||
namespace = "io.element.android.features.home.api"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.api
|
||||
package io.element.android.features.home.api
|
||||
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
|
|
@ -13,7 +13,7 @@ import com.bumble.appyx.core.plugin.Plugin
|
|||
import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
interface RoomListEntryPoint : FeatureEntryPoint {
|
||||
interface HomeEntryPoint : FeatureEntryPoint {
|
||||
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
|
||||
interface NodeBuilder {
|
||||
fun callback(callback: Callback): NodeBuilder
|
||||
|
|
@ -13,7 +13,7 @@ plugins {
|
|||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.roomlist.impl"
|
||||
namespace = "io.element.android.features.home.impl"
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
|
|
@ -51,8 +51,10 @@ dependencies {
|
|||
implementation(projects.features.rageshake.api)
|
||||
implementation(projects.services.analytics.api)
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
implementation(libs.haze)
|
||||
implementation(libs.haze.materials)
|
||||
implementation(projects.features.reportroom.api)
|
||||
api(projects.features.roomlist.api)
|
||||
api(projects.features.home.api)
|
||||
|
||||
testImplementation(libs.androidx.compose.ui.test.junit)
|
||||
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
||||
|
|
@ -5,30 +5,30 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl
|
||||
package io.element.android.features.home.impl
|
||||
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.roomlist.api.RoomListEntryPoint
|
||||
import io.element.android.features.home.api.HomeEntryPoint
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultRoomListEntryPoint @Inject constructor() : RoomListEntryPoint {
|
||||
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): RoomListEntryPoint.NodeBuilder {
|
||||
class DefaultHomeEntryPoint @Inject constructor() : HomeEntryPoint {
|
||||
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): HomeEntryPoint.NodeBuilder {
|
||||
val plugins = ArrayList<Plugin>()
|
||||
|
||||
return object : RoomListEntryPoint.NodeBuilder {
|
||||
override fun callback(callback: RoomListEntryPoint.Callback): RoomListEntryPoint.NodeBuilder {
|
||||
return object : HomeEntryPoint.NodeBuilder {
|
||||
override fun callback(callback: HomeEntryPoint.Callback): HomeEntryPoint.NodeBuilder {
|
||||
plugins += callback
|
||||
return this
|
||||
}
|
||||
|
||||
override fun build(): Node {
|
||||
return parentNode.createNode<RoomListFlowNode>(buildContext, plugins)
|
||||
return parentNode.createNode<HomeFlowNode>(buildContext, plugins)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.home.impl
|
||||
|
||||
sealed interface HomeEvents {
|
||||
data class SelectHomeNavigationBarItem(val item: HomeNavigationBarItem) : HomeEvents
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl
|
||||
package io.element.android.features.home.impl
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Parcelable
|
||||
|
|
@ -24,14 +24,14 @@ import dagger.assisted.Assisted
|
|||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.features.analytics.plan.MobileScreen
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.features.home.api.HomeEntryPoint
|
||||
import io.element.android.features.home.impl.components.RoomListMenuAction
|
||||
import io.element.android.features.home.impl.model.RoomListRoomSummary
|
||||
import io.element.android.features.invite.api.InviteData
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteView
|
||||
import io.element.android.features.invite.api.declineandblock.DeclineInviteAndBlockEntryPoint
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutView
|
||||
import io.element.android.features.reportroom.api.ReportRoomEntryPoint
|
||||
import io.element.android.features.roomlist.api.RoomListEntryPoint
|
||||
import io.element.android.features.roomlist.impl.components.RoomListMenuAction
|
||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||
import io.element.android.libraries.architecture.BackstackView
|
||||
import io.element.android.libraries.architecture.BaseFlowNode
|
||||
import io.element.android.libraries.deeplink.usecase.InviteFriendsUseCase
|
||||
|
|
@ -41,17 +41,17 @@ import io.element.android.services.analytics.api.AnalyticsService
|
|||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
class RoomListFlowNode @AssistedInject constructor(
|
||||
class HomeFlowNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val presenter: RoomListPresenter,
|
||||
private val presenter: HomePresenter,
|
||||
private val inviteFriendsUseCase: InviteFriendsUseCase,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val acceptDeclineInviteView: AcceptDeclineInviteView,
|
||||
private val directLogoutView: DirectLogoutView,
|
||||
private val reportRoomEntryPoint: ReportRoomEntryPoint,
|
||||
private val declineInviteAndBlockUserEntryPoint: DeclineInviteAndBlockEntryPoint,
|
||||
) : BaseFlowNode<RoomListFlowNode.NavTarget>(
|
||||
) : BaseFlowNode<HomeFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
initialElement = NavTarget.Root,
|
||||
savedStateMap = buildContext.savedStateMap,
|
||||
|
|
@ -79,27 +79,27 @@ class RoomListFlowNode @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun onRoomClick(roomId: RoomId) {
|
||||
plugins<RoomListEntryPoint.Callback>().forEach { it.onRoomClick(roomId) }
|
||||
plugins<HomeEntryPoint.Callback>().forEach { it.onRoomClick(roomId) }
|
||||
}
|
||||
|
||||
private fun onOpenSettings() {
|
||||
plugins<RoomListEntryPoint.Callback>().forEach { it.onSettingsClick() }
|
||||
plugins<HomeEntryPoint.Callback>().forEach { it.onSettingsClick() }
|
||||
}
|
||||
|
||||
private fun onCreateRoomClick() {
|
||||
plugins<RoomListEntryPoint.Callback>().forEach { it.onCreateRoomClick() }
|
||||
plugins<HomeEntryPoint.Callback>().forEach { it.onCreateRoomClick() }
|
||||
}
|
||||
|
||||
private fun onSetUpRecoveryClick() {
|
||||
plugins<RoomListEntryPoint.Callback>().forEach { it.onSetUpRecoveryClick() }
|
||||
plugins<HomeEntryPoint.Callback>().forEach { it.onSetUpRecoveryClick() }
|
||||
}
|
||||
|
||||
private fun onSessionConfirmRecoveryKeyClick() {
|
||||
plugins<RoomListEntryPoint.Callback>().forEach { it.onSessionConfirmRecoveryKeyClick() }
|
||||
plugins<HomeEntryPoint.Callback>().forEach { it.onSessionConfirmRecoveryKeyClick() }
|
||||
}
|
||||
|
||||
private fun onRoomSettingsClick(roomId: RoomId) {
|
||||
plugins<RoomListEntryPoint.Callback>().forEach { it.onRoomSettingsClick(roomId) }
|
||||
plugins<HomeEntryPoint.Callback>().forEach { it.onRoomSettingsClick(roomId) }
|
||||
}
|
||||
|
||||
private fun onReportRoomClick(roomId: RoomId) {
|
||||
|
|
@ -116,7 +116,7 @@ class RoomListFlowNode @AssistedInject constructor(
|
|||
inviteFriendsUseCase.execute(activity)
|
||||
}
|
||||
RoomListMenuAction.ReportBug -> {
|
||||
plugins<RoomListEntryPoint.Callback>().forEach { it.onReportBugClick() }
|
||||
plugins<HomeEntryPoint.Callback>().forEach { it.onReportBugClick() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -126,8 +126,8 @@ class RoomListFlowNode @AssistedInject constructor(
|
|||
val state = presenter.present()
|
||||
val activity = requireNotNull(LocalActivity.current)
|
||||
|
||||
RoomListView(
|
||||
state = state,
|
||||
HomeView(
|
||||
homeState = state,
|
||||
onRoomClick = this::onRoomClick,
|
||||
onSettingsClick = this::onOpenSettings,
|
||||
onCreateRoomClick = this::onCreateRoomClick,
|
||||
|
|
@ -140,7 +140,7 @@ class RoomListFlowNode @AssistedInject constructor(
|
|||
modifier = modifier,
|
||||
) {
|
||||
acceptDeclineInviteView.Render(
|
||||
state = state.acceptDeclineInviteState,
|
||||
state = state.roomListState.acceptDeclineInviteState,
|
||||
onAcceptInviteSuccess = this::onRoomClick,
|
||||
onDeclineInviteSuccess = { },
|
||||
modifier = Modifier
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.home.impl
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.runtime.Composable
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
|
||||
enum class HomeNavigationBarItem(
|
||||
@StringRes
|
||||
val labelRes: Int,
|
||||
) {
|
||||
Chats(
|
||||
labelRes = R.string.screen_roomlist_main_space_title
|
||||
),
|
||||
Spaces(
|
||||
// TODO Create a new entry in Localazy
|
||||
labelRes = R.string.screen_roomlist_main_space_title
|
||||
);
|
||||
|
||||
@Composable
|
||||
fun icon() = when (this) {
|
||||
Chats -> CompoundIcons.ChatSolid()
|
||||
// TODO Spaces -> CompoundIcons.Workspace()
|
||||
Spaces -> CompoundIcons.Code()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun from(index: Int): HomeNavigationBarItem {
|
||||
return entries.getOrElse(index) { Chats }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.home.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import io.element.android.features.home.impl.roomlist.RoomListState
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||
import io.element.android.features.rageshake.api.RageshakeFeatureAvailability
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.indicator.api.IndicatorService
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||
import javax.inject.Inject
|
||||
|
||||
class HomePresenter @Inject constructor(
|
||||
private val client: MatrixClient,
|
||||
private val syncService: SyncService,
|
||||
private val snackbarDispatcher: SnackbarDispatcher,
|
||||
private val indicatorService: IndicatorService,
|
||||
private val roomListPresenter: Presenter<RoomListState>,
|
||||
private val logoutPresenter: Presenter<DirectLogoutState>,
|
||||
private val rageshakeFeatureAvailability: RageshakeFeatureAvailability,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
) : Presenter<HomeState> {
|
||||
@Composable
|
||||
override fun present(): HomeState {
|
||||
val matrixUser = client.userProfile.collectAsState()
|
||||
val isOnline by syncService.isOnline.collectAsState()
|
||||
val canReportBug = remember { rageshakeFeatureAvailability.isAvailable() }
|
||||
val roomListState = roomListPresenter.present()
|
||||
val isSpaceFeatureEnabled by remember {
|
||||
featureFlagService.isFeatureEnabledFlow(FeatureFlags.Space)
|
||||
}.collectAsState(initial = false)
|
||||
var currentHomeNavigationBarItemOrdinal by rememberSaveable { mutableIntStateOf(HomeNavigationBarItem.Chats.ordinal) }
|
||||
val currentHomeNavigationBarItem by remember {
|
||||
derivedStateOf {
|
||||
HomeNavigationBarItem.from(currentHomeNavigationBarItemOrdinal)
|
||||
}
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
// Force a refresh of the profile
|
||||
client.getUserProfile()
|
||||
}
|
||||
// Avatar indicator
|
||||
val showAvatarIndicator by indicatorService.showRoomListTopBarIndicator()
|
||||
val directLogoutState = logoutPresenter.present()
|
||||
|
||||
fun handleEvents(event: HomeEvents) {
|
||||
when (event) {
|
||||
is HomeEvents.SelectHomeNavigationBarItem -> {
|
||||
currentHomeNavigationBarItemOrdinal = event.item.ordinal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState()
|
||||
return HomeState(
|
||||
matrixUser = matrixUser.value,
|
||||
showAvatarIndicator = showAvatarIndicator,
|
||||
hasNetworkConnection = isOnline,
|
||||
currentHomeNavigationBarItem = currentHomeNavigationBarItem,
|
||||
roomListState = roomListState,
|
||||
snackbarMessage = snackbarMessage,
|
||||
canReportBug = canReportBug,
|
||||
directLogoutState = directLogoutState,
|
||||
isSpaceFeatureEnabled = isSpaceFeatureEnabled,
|
||||
eventSink = ::handleEvents,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.home.impl
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.features.home.impl.roomlist.RoomListState
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
|
||||
@Immutable
|
||||
data class HomeState(
|
||||
val matrixUser: MatrixUser,
|
||||
val showAvatarIndicator: Boolean,
|
||||
val hasNetworkConnection: Boolean,
|
||||
val currentHomeNavigationBarItem: HomeNavigationBarItem,
|
||||
val roomListState: RoomListState,
|
||||
val snackbarMessage: SnackbarMessage?,
|
||||
val canReportBug: Boolean,
|
||||
val directLogoutState: DirectLogoutState,
|
||||
val isSpaceFeatureEnabled: Boolean,
|
||||
val eventSink: (HomeEvents) -> Unit,
|
||||
) {
|
||||
val displayActions = currentHomeNavigationBarItem == HomeNavigationBarItem.Chats
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.home.impl
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.home.impl.roomlist.RoomListState
|
||||
import io.element.android.features.home.impl.roomlist.RoomListStateProvider
|
||||
import io.element.android.features.home.impl.roomlist.aRoomListState
|
||||
import io.element.android.features.home.impl.roomlist.aRoomsContentState
|
||||
import io.element.android.features.home.impl.roomlist.generateRoomListRoomSummaryList
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||
import io.element.android.features.logout.api.direct.aDirectLogoutState
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
open class HomeStateProvider : PreviewParameterProvider<HomeState> {
|
||||
override val values: Sequence<HomeState>
|
||||
get() = sequenceOf(
|
||||
aHomeState(),
|
||||
aHomeState(hasNetworkConnection = false),
|
||||
aHomeState(snackbarMessage = SnackbarMessage(CommonStrings.common_verification_complete)),
|
||||
aHomeState(
|
||||
isSpaceFeatureEnabled = true,
|
||||
roomListState = aRoomListState(
|
||||
// Add more rooms to see the blur effect under the NavigationBar
|
||||
contentState = aRoomsContentState(
|
||||
summaries = generateRoomListRoomSummaryList(),
|
||||
)
|
||||
),
|
||||
),
|
||||
aHomeState(
|
||||
isSpaceFeatureEnabled = true,
|
||||
currentHomeNavigationBarItem = HomeNavigationBarItem.Spaces,
|
||||
),
|
||||
) + RoomListStateProvider().values.map {
|
||||
aHomeState(roomListState = it)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun aHomeState(
|
||||
matrixUser: MatrixUser = MatrixUser(userId = UserId("@id:domain"), displayName = "User#1"),
|
||||
showAvatarIndicator: Boolean = false,
|
||||
hasNetworkConnection: Boolean = true,
|
||||
snackbarMessage: SnackbarMessage? = null,
|
||||
currentHomeNavigationBarItem: HomeNavigationBarItem = HomeNavigationBarItem.Chats,
|
||||
roomListState: RoomListState = aRoomListState(),
|
||||
canReportBug: Boolean = true,
|
||||
isSpaceFeatureEnabled: Boolean = false,
|
||||
directLogoutState: DirectLogoutState = aDirectLogoutState(),
|
||||
eventSink: (HomeEvents) -> Unit = {}
|
||||
) = HomeState(
|
||||
matrixUser = matrixUser,
|
||||
showAvatarIndicator = showAvatarIndicator,
|
||||
hasNetworkConnection = hasNetworkConnection,
|
||||
snackbarMessage = snackbarMessage,
|
||||
canReportBug = canReportBug,
|
||||
directLogoutState = directLogoutState,
|
||||
currentHomeNavigationBarItem = currentHomeNavigationBarItem,
|
||||
roomListState = roomListState,
|
||||
isSpaceFeatureEnabled = isSpaceFeatureEnabled,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
|
@ -5,10 +5,15 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl
|
||||
@file:OptIn(ExperimentalHazeMaterialsApi::class)
|
||||
|
||||
package io.element.android.features.home.impl
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.calculateEndPadding
|
||||
import androidx.compose.foundation.layout.calculateStartPadding
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
|
@ -19,32 +24,48 @@ import androidx.compose.material3.rememberTopAppBarState
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import dev.chrisbanes.haze.hazeEffect
|
||||
import dev.chrisbanes.haze.hazeSource
|
||||
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
||||
import dev.chrisbanes.haze.materials.HazeMaterials
|
||||
import dev.chrisbanes.haze.rememberHazeState
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.home.impl.components.RoomListContentView
|
||||
import io.element.android.features.home.impl.components.RoomListMenuAction
|
||||
import io.element.android.features.home.impl.components.RoomListTopBar
|
||||
import io.element.android.features.home.impl.model.RoomListRoomSummary
|
||||
import io.element.android.features.home.impl.roomlist.RoomListContextMenu
|
||||
import io.element.android.features.home.impl.roomlist.RoomListDeclineInviteMenu
|
||||
import io.element.android.features.home.impl.roomlist.RoomListEvents
|
||||
import io.element.android.features.home.impl.roomlist.RoomListState
|
||||
import io.element.android.features.home.impl.search.RoomListSearchView
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomView
|
||||
import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorContainer
|
||||
import io.element.android.features.roomlist.impl.components.RoomListContentView
|
||||
import io.element.android.features.roomlist.impl.components.RoomListMenuAction
|
||||
import io.element.android.features.roomlist.impl.components.RoomListTopBar
|
||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||
import io.element.android.features.roomlist.impl.search.RoomListSearchView
|
||||
import io.element.android.libraries.androidutils.throttler.FirstThrottler
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.FloatingActionButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.NavigationBar
|
||||
import io.element.android.libraries.designsystem.theme.components.NavigationBarItem
|
||||
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.snackbar.SnackbarHost
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
@Composable
|
||||
fun RoomListView(
|
||||
state: RoomListState,
|
||||
fun HomeView(
|
||||
homeState: HomeState,
|
||||
onRoomClick: (RoomId) -> Unit,
|
||||
onSettingsClick: () -> Unit,
|
||||
onSetUpRecoveryClick: () -> Unit,
|
||||
|
|
@ -57,12 +78,13 @@ fun RoomListView(
|
|||
modifier: Modifier = Modifier,
|
||||
acceptDeclineInviteView: @Composable () -> Unit,
|
||||
) {
|
||||
val state: RoomListState = homeState.roomListState
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val firstThrottler = remember { FirstThrottler(300, coroutineScope) }
|
||||
|
||||
ConnectivityIndicatorContainer(
|
||||
modifier = modifier,
|
||||
isOnline = state.hasNetworkConnection,
|
||||
isOnline = homeState.hasNetworkConnection,
|
||||
) { topPadding ->
|
||||
Box {
|
||||
if (state.contextMenu is RoomListState.ContextMenu.Shown) {
|
||||
|
|
@ -85,8 +107,8 @@ fun RoomListView(
|
|||
|
||||
LeaveRoomView(state = state.leaveRoomState)
|
||||
|
||||
RoomListScaffold(
|
||||
state = state,
|
||||
HomeScaffold(
|
||||
state = homeState,
|
||||
onSetUpRecoveryClick = onSetUpRecoveryClick,
|
||||
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
|
||||
onRoomClick = { if (firstThrottler.canHandle()) onRoomClick(it) },
|
||||
|
|
@ -114,8 +136,8 @@ fun RoomListView(
|
|||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun RoomListScaffold(
|
||||
state: RoomListState,
|
||||
private fun HomeScaffold(
|
||||
state: HomeState,
|
||||
onSetUpRecoveryClick: () -> Unit,
|
||||
onConfirmRecoveryKeyClick: () -> Unit,
|
||||
onRoomClick: (RoomId) -> Unit,
|
||||
|
|
@ -131,38 +153,106 @@ private fun RoomListScaffold(
|
|||
val appBarState = rememberTopAppBarState()
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(appBarState)
|
||||
val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage)
|
||||
val roomListState: RoomListState = state.roomListState
|
||||
|
||||
BackHandler(
|
||||
enabled = state.currentHomeNavigationBarItem != HomeNavigationBarItem.Chats,
|
||||
) {
|
||||
state.eventSink(HomeEvents.SelectHomeNavigationBarItem(HomeNavigationBarItem.Chats))
|
||||
}
|
||||
|
||||
val hazeState = rememberHazeState()
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
topBar = {
|
||||
RoomListTopBar(
|
||||
title = stringResource(state.currentHomeNavigationBarItem.labelRes),
|
||||
matrixUser = state.matrixUser,
|
||||
showAvatarIndicator = state.showAvatarIndicator,
|
||||
areSearchResultsDisplayed = state.searchState.isSearchActive,
|
||||
onToggleSearch = { state.eventSink(RoomListEvents.ToggleSearchResults) },
|
||||
areSearchResultsDisplayed = roomListState.searchState.isSearchActive,
|
||||
onToggleSearch = { roomListState.eventSink(RoomListEvents.ToggleSearchResults) },
|
||||
onMenuActionClick = onMenuActionClick,
|
||||
onOpenSettings = onOpenSettings,
|
||||
scrollBehavior = scrollBehavior,
|
||||
displayMenuItems = state.displayActions,
|
||||
displayFilters = state.displayFilters,
|
||||
filtersState = state.filtersState,
|
||||
displayFilters = roomListState.displayFilters && state.currentHomeNavigationBarItem == HomeNavigationBarItem.Chats,
|
||||
filtersState = roomListState.filtersState,
|
||||
canReportBug = state.canReportBug,
|
||||
)
|
||||
},
|
||||
bottomBar = {
|
||||
if (state.isSpaceFeatureEnabled) {
|
||||
NavigationBar(
|
||||
containerColor = Color.Transparent,
|
||||
modifier = Modifier
|
||||
.hazeEffect(
|
||||
state = hazeState,
|
||||
style = HazeMaterials.regular(),
|
||||
)
|
||||
) {
|
||||
HomeNavigationBarItem.entries.forEach { item ->
|
||||
NavigationBarItem(
|
||||
selected = state.currentHomeNavigationBarItem == item,
|
||||
onClick = {
|
||||
state.eventSink(HomeEvents.SelectHomeNavigationBarItem(item))
|
||||
},
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = item.icon(),
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
label = {
|
||||
Text(stringResource(item.labelRes))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
content = { padding ->
|
||||
RoomListContentView(
|
||||
contentState = state.contentState,
|
||||
filtersState = state.filtersState,
|
||||
hideInvitesAvatars = state.hideInvitesAvatars,
|
||||
eventSink = state.eventSink,
|
||||
onSetUpRecoveryClick = onSetUpRecoveryClick,
|
||||
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
|
||||
onRoomClick = ::onRoomClick,
|
||||
onCreateRoomClick = onCreateRoomClick,
|
||||
modifier = Modifier
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
)
|
||||
when (state.currentHomeNavigationBarItem) {
|
||||
HomeNavigationBarItem.Chats -> {
|
||||
RoomListContentView(
|
||||
contentState = roomListState.contentState,
|
||||
filtersState = roomListState.filtersState,
|
||||
hideInvitesAvatars = roomListState.hideInvitesAvatars,
|
||||
eventSink = roomListState.eventSink,
|
||||
onSetUpRecoveryClick = onSetUpRecoveryClick,
|
||||
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
|
||||
onRoomClick = ::onRoomClick,
|
||||
onCreateRoomClick = onCreateRoomClick,
|
||||
// FAB height is 56dp, bottom padding is 16dp, we add 8dp as extra margin -> 56+16+8 = 80,
|
||||
// and include provided bottom padding
|
||||
contentBottomPadding = 80.dp + padding.calculateBottomPadding(),
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
top = padding.calculateTopPadding(),
|
||||
bottom = 0.dp,
|
||||
start = padding.calculateStartPadding(LocalLayoutDirection.current),
|
||||
end = padding.calculateEndPadding(LocalLayoutDirection.current),
|
||||
)
|
||||
.consumeWindowInsets(padding)
|
||||
.hazeSource(state = hazeState)
|
||||
)
|
||||
}
|
||||
HomeNavigationBarItem.Spaces -> {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.align(Alignment.Center),
|
||||
text = "Spaces are coming soon!",
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
floatingActionButton = {
|
||||
if (state.displayActions) {
|
||||
|
|
@ -186,9 +276,9 @@ internal fun RoomListRoomSummary.contentType() = displayType.ordinal
|
|||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun RoomListViewPreview(@PreviewParameter(RoomListStateProvider::class) state: RoomListState) = ElementPreview {
|
||||
RoomListView(
|
||||
state = state,
|
||||
internal fun HomeViewPreview(@PreviewParameter(HomeStateProvider::class) state: HomeState) = ElementPreview {
|
||||
HomeView(
|
||||
homeState = state,
|
||||
onRoomClick = {},
|
||||
onSettingsClick = {},
|
||||
onSetUpRecoveryClick = {},
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.components
|
||||
package io.element.android.features.home.impl.components
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -5,12 +5,12 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.components
|
||||
package io.element.android.features.home.impl.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import io.element.android.features.roomlist.impl.R
|
||||
import io.element.android.features.home.impl.R
|
||||
import io.element.android.libraries.designsystem.components.Announcement
|
||||
import io.element.android.libraries.designsystem.components.AnnouncementType
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
|
|
@ -5,12 +5,12 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.components
|
||||
package io.element.android.features.home.impl.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import io.element.android.features.roomlist.impl.R
|
||||
import io.element.android.features.home.impl.R
|
||||
import io.element.android.libraries.designsystem.components.Announcement
|
||||
import io.element.android.libraries.designsystem.components.AnnouncementType
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
|
|
@ -5,12 +5,12 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.components
|
||||
package io.element.android.features.home.impl.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import io.element.android.features.roomlist.impl.R
|
||||
import io.element.android.features.home.impl.R
|
||||
import io.element.android.libraries.designsystem.components.Announcement
|
||||
import io.element.android.libraries.designsystem.components.AnnouncementType
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.components
|
||||
package io.element.android.features.home.impl.components
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
|
|
@ -31,22 +31,23 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.roomlist.impl.R
|
||||
import io.element.android.features.roomlist.impl.RoomListContentState
|
||||
import io.element.android.features.roomlist.impl.RoomListContentStateProvider
|
||||
import io.element.android.features.roomlist.impl.RoomListEvents
|
||||
import io.element.android.features.roomlist.impl.SecurityBannerState
|
||||
import io.element.android.features.roomlist.impl.contentType
|
||||
import io.element.android.features.roomlist.impl.filters.RoomListFilter
|
||||
import io.element.android.features.roomlist.impl.filters.RoomListFiltersEmptyStateResources
|
||||
import io.element.android.features.roomlist.impl.filters.RoomListFiltersState
|
||||
import io.element.android.features.roomlist.impl.filters.aRoomListFiltersState
|
||||
import io.element.android.features.roomlist.impl.filters.selection.FilterSelectionState
|
||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||
import io.element.android.features.roomlist.impl.model.RoomSummaryDisplayType
|
||||
import io.element.android.features.home.impl.R
|
||||
import io.element.android.features.home.impl.contentType
|
||||
import io.element.android.features.home.impl.filters.RoomListFilter
|
||||
import io.element.android.features.home.impl.filters.RoomListFiltersEmptyStateResources
|
||||
import io.element.android.features.home.impl.filters.RoomListFiltersState
|
||||
import io.element.android.features.home.impl.filters.aRoomListFiltersState
|
||||
import io.element.android.features.home.impl.filters.selection.FilterSelectionState
|
||||
import io.element.android.features.home.impl.model.RoomListRoomSummary
|
||||
import io.element.android.features.home.impl.model.RoomSummaryDisplayType
|
||||
import io.element.android.features.home.impl.roomlist.RoomListContentState
|
||||
import io.element.android.features.home.impl.roomlist.RoomListContentStateProvider
|
||||
import io.element.android.features.home.impl.roomlist.RoomListEvents
|
||||
import io.element.android.features.home.impl.roomlist.SecurityBannerState
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
|
|
@ -66,6 +67,7 @@ fun RoomListContentView(
|
|||
onConfirmRecoveryKeyClick: () -> Unit,
|
||||
onRoomClick: (RoomListRoomSummary) -> Unit,
|
||||
onCreateRoomClick: () -> Unit,
|
||||
contentBottomPadding: Dp,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(modifier = modifier) {
|
||||
|
|
@ -93,6 +95,7 @@ fun RoomListContentView(
|
|||
onSetUpRecoveryClick = onSetUpRecoveryClick,
|
||||
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
|
||||
onRoomClick = onRoomClick,
|
||||
contentBottomPadding = contentBottomPadding,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -164,6 +167,7 @@ private fun RoomsView(
|
|||
onSetUpRecoveryClick: () -> Unit,
|
||||
onConfirmRecoveryKeyClick: () -> Unit,
|
||||
onRoomClick: (RoomListRoomSummary) -> Unit,
|
||||
contentBottomPadding: Dp,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (state.summaries.isEmpty() && filtersState.hasAnyFilterSelected) {
|
||||
|
|
@ -179,6 +183,7 @@ private fun RoomsView(
|
|||
onSetUpRecoveryClick = onSetUpRecoveryClick,
|
||||
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
|
||||
onRoomClick = onRoomClick,
|
||||
contentBottomPadding = contentBottomPadding,
|
||||
modifier = modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
|
|
@ -192,6 +197,7 @@ private fun RoomsViewList(
|
|||
onSetUpRecoveryClick: () -> Unit,
|
||||
onConfirmRecoveryKeyClick: () -> Unit,
|
||||
onRoomClick: (RoomListRoomSummary) -> Unit,
|
||||
contentBottomPadding: Dp,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val lazyListState = rememberLazyListState()
|
||||
|
|
@ -210,8 +216,7 @@ private fun RoomsViewList(
|
|||
LazyColumn(
|
||||
state = lazyListState,
|
||||
modifier = modifier,
|
||||
// FAB height is 56dp, bottom padding is 16dp, we add 8dp as extra margin -> 56+16+8 = 80
|
||||
contentPadding = PaddingValues(bottom = 80.dp)
|
||||
contentPadding = PaddingValues(bottom = contentBottomPadding)
|
||||
) {
|
||||
when (state.securityBannerState) {
|
||||
SecurityBannerState.SetUpRecovery -> {
|
||||
|
|
@ -311,7 +316,12 @@ internal fun RoomListContentViewPreview(@PreviewParameter(RoomListContentStatePr
|
|||
RoomListContentView(
|
||||
contentState = state,
|
||||
filtersState = aRoomListFiltersState(
|
||||
filterSelectionStates = RoomListFilter.entries.map { FilterSelectionState(it, isSelected = true) }
|
||||
filterSelectionStates = RoomListFilter.entries.map {
|
||||
FilterSelectionState(
|
||||
filter = it,
|
||||
isSelected = true
|
||||
)
|
||||
}
|
||||
),
|
||||
hideInvitesAvatars = false,
|
||||
eventSink = {},
|
||||
|
|
@ -319,5 +329,6 @@ internal fun RoomListContentViewPreview(@PreviewParameter(RoomListContentStatePr
|
|||
onConfirmRecoveryKeyClick = {},
|
||||
onRoomClick = {},
|
||||
onCreateRoomClick = {},
|
||||
contentBottomPadding = 0.dp,
|
||||
)
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.components
|
||||
package io.element.android.features.home.impl.components
|
||||
|
||||
enum class RoomListMenuAction {
|
||||
InviteFriends,
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.components
|
||||
package io.element.android.features.home.impl.components
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -34,20 +34,23 @@ import androidx.compose.ui.layout.onSizeChanged
|
|||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.heading
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.DpSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.appconfig.RoomListConfig
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.roomlist.impl.R
|
||||
import io.element.android.features.roomlist.impl.filters.RoomListFiltersState
|
||||
import io.element.android.features.roomlist.impl.filters.RoomListFiltersView
|
||||
import io.element.android.features.roomlist.impl.filters.aRoomListFiltersState
|
||||
import io.element.android.features.home.impl.R
|
||||
import io.element.android.features.home.impl.filters.RoomListFiltersState
|
||||
import io.element.android.features.home.impl.filters.RoomListFiltersView
|
||||
import io.element.android.features.home.impl.filters.aRoomListFiltersState
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.RedIndicatorAtom
|
||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
||||
import io.element.android.libraries.designsystem.components.avatarBloom
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
|
|
@ -75,6 +78,7 @@ private val avatarBloomSize = 430.dp
|
|||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun RoomListTopBar(
|
||||
title: String,
|
||||
matrixUser: MatrixUser,
|
||||
showAvatarIndicator: Boolean,
|
||||
areSearchResultsDisplayed: Boolean,
|
||||
|
|
@ -89,6 +93,7 @@ fun RoomListTopBar(
|
|||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
DefaultRoomListTopBar(
|
||||
title = title,
|
||||
matrixUser = matrixUser,
|
||||
showAvatarIndicator = showAvatarIndicator,
|
||||
areSearchResultsDisplayed = areSearchResultsDisplayed,
|
||||
|
|
@ -107,6 +112,7 @@ fun RoomListTopBar(
|
|||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun DefaultRoomListTopBar(
|
||||
title: String,
|
||||
matrixUser: MatrixUser,
|
||||
showAvatarIndicator: Boolean,
|
||||
areSearchResultsDisplayed: Boolean,
|
||||
|
|
@ -193,7 +199,12 @@ private fun DefaultRoomListTopBar(
|
|||
scrolledContainerColor = Color.Transparent,
|
||||
),
|
||||
title = {
|
||||
Text(text = stringResource(id = R.string.screen_roomlist_main_space_title))
|
||||
Text(
|
||||
modifier = Modifier.semantics {
|
||||
heading()
|
||||
},
|
||||
text = title,
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
NavigationIcon(
|
||||
|
|
@ -297,6 +308,7 @@ private fun NavigationIcon(
|
|||
Box {
|
||||
Avatar(
|
||||
avatarData = avatarData,
|
||||
avatarType = AvatarType.User,
|
||||
contentDescription = stringResource(CommonStrings.common_settings),
|
||||
)
|
||||
if (showAvatarIndicator) {
|
||||
|
|
@ -313,6 +325,7 @@ private fun NavigationIcon(
|
|||
@Composable
|
||||
internal fun DefaultRoomListTopBarPreview() = ElementPreview {
|
||||
DefaultRoomListTopBar(
|
||||
title = stringResource(R.string.screen_roomlist_main_space_title),
|
||||
matrixUser = MatrixUser(UserId("@id:domain"), "Alice"),
|
||||
showAvatarIndicator = false,
|
||||
areSearchResultsDisplayed = false,
|
||||
|
|
@ -332,6 +345,7 @@ internal fun DefaultRoomListTopBarPreview() = ElementPreview {
|
|||
@Composable
|
||||
internal fun DefaultRoomListTopBarWithIndicatorPreview() = ElementPreview {
|
||||
DefaultRoomListTopBar(
|
||||
title = stringResource(R.string.screen_roomlist_main_space_title),
|
||||
matrixUser = MatrixUser(UserId("@id:domain"), "Alice"),
|
||||
showAvatarIndicator = true,
|
||||
areSearchResultsDisplayed = false,
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.components
|
||||
package io.element.android.features.home.impl.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.components
|
||||
package io.element.android.features.home.impl.components
|
||||
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
|
|
@ -37,14 +37,15 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.roomlist.impl.R
|
||||
import io.element.android.features.roomlist.impl.RoomListEvents
|
||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummaryProvider
|
||||
import io.element.android.features.roomlist.impl.model.RoomSummaryDisplayType
|
||||
import io.element.android.features.home.impl.R
|
||||
import io.element.android.features.home.impl.model.RoomListRoomSummary
|
||||
import io.element.android.features.home.impl.model.RoomListRoomSummaryProvider
|
||||
import io.element.android.features.home.impl.model.RoomSummaryDisplayType
|
||||
import io.element.android.features.home.impl.roomlist.RoomListEvents
|
||||
import io.element.android.libraries.core.extensions.orEmpty
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.UnreadIndicatorAtom
|
||||
import io.element.android.libraries.designsystem.components.avatar.RoomAvatar
|
||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
|
|
@ -172,6 +173,7 @@ private fun RoomSummaryScaffoldRow(
|
|||
val clickModifier = Modifier.combinedClickable(
|
||||
onClick = { onClick(room) },
|
||||
onLongClick = { onLongClick(room) },
|
||||
onLongClickLabel = stringResource(CommonStrings.action_open_context_menu),
|
||||
indication = ripple(),
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
)
|
||||
|
|
@ -184,11 +186,13 @@ private fun RoomSummaryScaffoldRow(
|
|||
.padding(horizontal = 16.dp, vertical = 11.dp)
|
||||
.height(IntrinsicSize.Min),
|
||||
) {
|
||||
RoomAvatar(
|
||||
Avatar(
|
||||
avatarData = room.avatarData,
|
||||
heroes = room.heroes,
|
||||
isTombstoned = room.isTombstoned,
|
||||
hideAvatarImage = hideAvatarImage,
|
||||
avatarType = AvatarType.Room(
|
||||
heroes = room.heroes,
|
||||
isTombstoned = room.isTombstoned,
|
||||
),
|
||||
hideImage = hideAvatarImage,
|
||||
)
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Column(
|
||||
|
|
@ -5,12 +5,12 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.components
|
||||
package io.element.android.features.home.impl.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import io.element.android.features.roomlist.impl.R
|
||||
import io.element.android.features.home.impl.R
|
||||
import io.element.android.libraries.designsystem.components.Announcement
|
||||
import io.element.android.libraries.designsystem.components.AnnouncementType
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
|
|
@ -5,9 +5,9 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.datasource
|
||||
package io.element.android.features.home.impl.datasource
|
||||
|
||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||
import io.element.android.features.home.impl.model.RoomListRoomSummary
|
||||
import io.element.android.libraries.androidutils.diff.DiffCacheUpdater
|
||||
import io.element.android.libraries.androidutils.diff.MutableListDiffCache
|
||||
import io.element.android.libraries.androidutils.system.DateTimeObserver
|
||||
|
|
@ -5,10 +5,10 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.datasource
|
||||
package io.element.android.features.home.impl.datasource
|
||||
|
||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||
import io.element.android.features.roomlist.impl.model.RoomSummaryDisplayType
|
||||
import io.element.android.features.home.impl.model.RoomListRoomSummary
|
||||
import io.element.android.features.home.impl.model.RoomSummaryDisplayType
|
||||
import io.element.android.libraries.core.extensions.orEmpty
|
||||
import io.element.android.libraries.dateformatter.api.DateFormatter
|
||||
import io.element.android.libraries.dateformatter.api.DateFormatterMode
|
||||
|
|
@ -5,21 +5,26 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.di
|
||||
package io.element.android.features.home.impl.di
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesTo
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import io.element.android.features.roomlist.impl.filters.RoomListFiltersPresenter
|
||||
import io.element.android.features.roomlist.impl.filters.RoomListFiltersState
|
||||
import io.element.android.features.roomlist.impl.search.RoomListSearchPresenter
|
||||
import io.element.android.features.roomlist.impl.search.RoomListSearchState
|
||||
import io.element.android.features.home.impl.filters.RoomListFiltersPresenter
|
||||
import io.element.android.features.home.impl.filters.RoomListFiltersState
|
||||
import io.element.android.features.home.impl.roomlist.RoomListPresenter
|
||||
import io.element.android.features.home.impl.roomlist.RoomListState
|
||||
import io.element.android.features.home.impl.search.RoomListSearchPresenter
|
||||
import io.element.android.features.home.impl.search.RoomListSearchState
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
|
||||
@ContributesTo(SessionScope::class)
|
||||
@Module
|
||||
interface RoomListModule {
|
||||
@Binds
|
||||
fun bindRoomListPresenter(presenter: RoomListPresenter): Presenter<RoomListState>
|
||||
|
||||
@Binds
|
||||
fun bindSearchPresenter(presenter: RoomListSearchPresenter): Presenter<RoomListSearchState>
|
||||
|
||||
|
|
@ -5,9 +5,9 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.filters
|
||||
package io.element.android.features.home.impl.filters
|
||||
|
||||
import io.element.android.features.roomlist.impl.R
|
||||
import io.element.android.features.home.impl.R
|
||||
|
||||
/**
|
||||
* Enum class representing the different filters that can be applied to the room list.
|
||||
|
|
@ -5,10 +5,10 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.filters
|
||||
package io.element.android.features.home.impl.filters
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import io.element.android.features.roomlist.impl.R
|
||||
import io.element.android.features.home.impl.R
|
||||
|
||||
/**
|
||||
* Holds the resources for the empty state when filters are applied to the room list.
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.filters
|
||||
package io.element.android.features.home.impl.filters
|
||||
|
||||
sealed interface RoomListFiltersEvents {
|
||||
data class ToggleFilter(val filter: RoomListFilter) : RoomListFiltersEvents
|
||||
|
|
@ -5,12 +5,12 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.filters
|
||||
package io.element.android.features.home.impl.filters
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.produceState
|
||||
import io.element.android.features.roomlist.impl.filters.selection.FilterSelectionStrategy
|
||||
import io.element.android.features.home.impl.filters.selection.FilterSelectionStrategy
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
|
@ -5,9 +5,9 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.filters
|
||||
package io.element.android.features.home.impl.filters
|
||||
|
||||
import io.element.android.features.roomlist.impl.filters.selection.FilterSelectionState
|
||||
import io.element.android.features.home.impl.filters.selection.FilterSelectionState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
|
|
@ -5,10 +5,10 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.filters
|
||||
package io.element.android.features.home.impl.filters
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.roomlist.impl.filters.selection.FilterSelectionState
|
||||
import io.element.android.features.home.impl.filters.selection.FilterSelectionState
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
class RoomListFiltersStateProvider : PreviewParameterProvider<RoomListFiltersState> {
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.filters
|
||||
package io.element.android.features.home.impl.filters
|
||||
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.Spring
|
||||
|
|
@ -40,6 +40,7 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.compose.ui.zIndex
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.home.impl.R
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
|
|
@ -147,7 +148,7 @@ private fun RoomListClearFiltersButton(
|
|||
modifier = Modifier.align(Alignment.Center),
|
||||
imageVector = CompoundIcons.Close(),
|
||||
tint = ElementTheme.colors.iconOnSolidPrimary,
|
||||
contentDescription = stringResource(id = io.element.android.libraries.ui.strings.R.string.action_clear),
|
||||
contentDescription = stringResource(id = R.string.screen_roomlist_clear_filters),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,10 +5,10 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.filters.selection
|
||||
package io.element.android.features.home.impl.filters.selection
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.roomlist.impl.filters.RoomListFilter
|
||||
import io.element.android.features.home.impl.filters.RoomListFilter
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import javax.inject.Inject
|
||||
|
|
@ -5,9 +5,9 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.filters.selection
|
||||
package io.element.android.features.home.impl.filters.selection
|
||||
|
||||
import io.element.android.features.roomlist.impl.filters.RoomListFilter
|
||||
import io.element.android.features.home.impl.filters.RoomListFilter
|
||||
|
||||
data class FilterSelectionState(
|
||||
val filter: RoomListFilter,
|
||||
|
|
@ -5,9 +5,9 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.filters.selection
|
||||
package io.element.android.features.home.impl.filters.selection
|
||||
|
||||
import io.element.android.features.roomlist.impl.filters.RoomListFilter
|
||||
import io.element.android.features.home.impl.filters.RoomListFilter
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface FilterSelectionStrategy {
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.model
|
||||
package io.element.android.features.home.impl.model
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.features.invite.api.InviteData
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.model
|
||||
package io.element.android.features.home.impl.model
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl.model
|
||||
package io.element.android.features.home.impl.model
|
||||
|
||||
/**
|
||||
* Represents the type of display for a room list item.
|
||||
|
|
@ -5,10 +5,10 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomlist.impl
|
||||
package io.element.android.features.home.impl.roomlist
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||
import io.element.android.features.home.impl.model.RoomListRoomSummary
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
|
||||
import io.element.android.libraries.fullscreenintent.api.aFullScreenIntentPermissionsState
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue