Merge branch 'release/25.08.0' into main
This commit is contained in:
commit
8bf377d65b
636 changed files with 3380 additions and 2459 deletions
|
|
@ -4,6 +4,6 @@ appId: ${MAESTRO_APP_ID}
|
|||
- tapOn:
|
||||
id: "text_editor"
|
||||
- inputText: "Hello world!"
|
||||
- tapOn: "Send"
|
||||
- tapOn: "Send message"
|
||||
- hideKeyboard
|
||||
- takeScreenshot: build/maestro/511-Timeline
|
||||
|
|
|
|||
|
|
@ -6,6 +6,9 @@ appId: ${MAESTRO_APP_ID}
|
|||
- runFlow: messages/text.yaml
|
||||
- runFlow: messages/location.yaml
|
||||
- runFlow: messages/poll.yaml
|
||||
- runFlow: call/call.yaml
|
||||
|
||||
# Restore once the call flow is fixed
|
||||
#- runFlow: call/call.yaml
|
||||
|
||||
- back
|
||||
- runFlow: ../../assertions/assertHomeDisplayed.yaml
|
||||
|
|
|
|||
48
CHANGES.md
48
CHANGES.md
|
|
@ -1,3 +1,51 @@
|
|||
Changes in Element X v25.07.1
|
||||
=============================
|
||||
|
||||
<!-- Release notes generated using configuration in .github/release.yml at v25.07.1 -->
|
||||
|
||||
## What's Changed
|
||||
### 🐛 Bugfixes
|
||||
* fix ( room list) : rebuild with filteredSummaries to avoid bad state by @ganfra in https://github.com/element-hq/element-x-android/pull/4993
|
||||
* Keep video rotation metadata when transcoding by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5008
|
||||
### 🗣 Translations
|
||||
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/4988
|
||||
### 🧱 Build
|
||||
* Update Gradle Wrapper from 8.14.2 to 8.14.3 by @ElementBot in https://github.com/element-hq/element-x-android/pull/4985
|
||||
* Stop ignoring dependencies, but instead set `open-pull-requests-limit to 0 by @bmarty in https://github.com/element-hq/element-x-android/pull/5013
|
||||
### 📄 Documentation
|
||||
* Update to the status and clarifications with respect to the legacy app. by @mxandreas in https://github.com/element-hq/element-x-android/pull/5016
|
||||
### 🚧 In development 🚧
|
||||
* Home navigation bar fixes by @bmarty in https://github.com/element-hq/element-x-android/pull/4990
|
||||
* Home screen iteration by @bmarty in https://github.com/element-hq/element-x-android/pull/5003
|
||||
### Dependency upgrades
|
||||
* Update dependency io.element.android:compound-android to v25.7.4 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/4984
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v25.7.7 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/4989
|
||||
* Update plugin ktlint to v13 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/4992
|
||||
* Update dependency org.jetbrains.kotlinx:kotlinx-datetime to v0.7.1-0.6.x-compat by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/4991
|
||||
* Update haze to v1.6.7 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/4987
|
||||
* Update dependency com.squareup.okhttp3:okhttp-bom to v5 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/4979
|
||||
* Update dependency io.sentry:sentry-android to v8.17.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/4998
|
||||
* Update dependency com.squareup.okhttp3:okhttp-bom to v5.1.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/4997
|
||||
* Update dependency org.maplibre.gl:android-sdk to v11.12.0 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5001
|
||||
* Update dependency com.posthog:posthog-android to v3.19.2 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5009
|
||||
* Update dependency org.maplibre.gl:android-sdk to v11.12.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5006
|
||||
* Update android.gradle.plugin to v8.11.1 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5014
|
||||
* Update rnkdsh/action-upload-diawi action to v1.5.10 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5019
|
||||
* Update wysiwyg to v2.38.5 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5025
|
||||
* Update haze to v1.6.8 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5026
|
||||
* Update dependency org.matrix.rustcomponents:sdk-android to v25.7.15 by @renovate[bot] in https://github.com/element-hq/element-x-android/pull/5011
|
||||
### Others
|
||||
* Remove bloom effect and replace by linear gradient by @bmarty in https://github.com/element-hq/element-x-android/pull/4926
|
||||
* misc (a11y) : mark MainActionButton icon as decorative by @ganfra in https://github.com/element-hq/element-x-android/pull/4996
|
||||
* Make `ContentAvoidingLayoutData` an immutable data class by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4999
|
||||
* Remove unused composable and cleanup colors by @bmarty in https://github.com/element-hq/element-x-android/pull/5000
|
||||
* Add a feature flag to reuse the last `pos` value for initial syncs by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5010
|
||||
* [a11y] Fix several issues around accessibility by @bmarty in https://github.com/element-hq/element-x-android/pull/5007
|
||||
* Replace video transcoder with Media3 Transformer by @jmartinesp in https://github.com/element-hq/element-x-android/pull/5018
|
||||
|
||||
|
||||
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v25.07.0...v25.07.1
|
||||
|
||||
Changes in Element X v25.07.0
|
||||
=============================
|
||||
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ allprojects {
|
|||
config.from(files("$rootDir/tools/detekt/detekt.yml"))
|
||||
}
|
||||
dependencies {
|
||||
detektPlugins("io.nlopez.compose.rules:detekt:0.4.22")
|
||||
detektPlugins("io.nlopez.compose.rules:detekt:0.4.26")
|
||||
detektPlugins(project(":tests:detekt-rules"))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
|
||||
This doc is a quick introduction about the project and its architecture.
|
||||
|
||||
It's aim is to help new developers to understand the overall project and where to start developing.
|
||||
Its aim is to help new developers to understand the overall project and where to start developing.
|
||||
|
||||
Other useful documentation:
|
||||
|
||||
|
|
@ -157,6 +157,8 @@ Troubleshooting:
|
|||
- If you get the error `Unsupported class file major version <n>`, try changing your JVM version by setting
|
||||
`JAVA_HOME` and, if building via Android Studio, "File | Settings | Build, Execution, Deployment | Build Tools | Gradle | Gradle JDK".
|
||||
|
||||
You can switch back to using the published version of the SDK by deleting `libraries/rustsdk/matrix-rust-sdk.aar`.
|
||||
|
||||
### The Android project
|
||||
|
||||
The project should compile out of the box.
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ That is why clients are able to **process the push rules client side** to decide
|
|||
|
||||
As seen previously, App developers don't directly send a push to the end user's device, they use a Push Provider as intermediary. So technically this intermediary is able to read the content of what is sent.
|
||||
|
||||
App developers usually mitigate this by sending a `silent notification`, that is a notification with no identifiable data, or with an encrypted payload. When the push is received the app can then synchronise to it's server in order to generate a local notification.
|
||||
App developers usually mitigate this by sending a `silent notification`, that is a notification with no identifiable data, or with an encrypted payload. When the push is received the app can then synchronise to its server in order to generate a local notification.
|
||||
|
||||
|
||||
### Background processing limitations
|
||||
|
|
|
|||
2
fastlane/metadata/android/en-US/changelogs/202508000.txt
Normal file
2
fastlane/metadata/android/en-US/changelogs/202508000.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
Main changes in this version: start support for room v12, bug fixes and general improvements.
|
||||
Full changelog: https://github.com/element-hq/element-x-android/releases
|
||||
|
|
@ -22,7 +22,7 @@ Choose where to host your data - from any public server (the largest free server
|
|||
Use Element everywhere. Stay in touch wherever you are with fully synchronised message history across all your devices, including on the web at https://app.element.io
|
||||
|
||||
<b>Element X is our next-generation app</b>
|
||||
If you’re using the original Element app, it’s time to try Element X! It’s faster, easier to use, and more powerful than the original app. It’s better in every way and we’re adding new features all the time.
|
||||
If you’re using the previous-generation Element Classic app, it’s time to try Element X! It’s faster, easier to use, and more powerful than the classic app. It’s better in every way and we’re adding new features all the time.
|
||||
|
||||
The application requires the android.permission.REQUEST_INSTALL_PACKAGES permission to enable the installation of applications received as attachments, ensuring seamless and convenient access to new software within the app.
|
||||
|
||||
|
|
|
|||
|
|
@ -29,13 +29,13 @@ import io.element.android.compound.theme.ElementTheme
|
|||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.analytics.api.AnalyticsOptInEvents
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.organisms.InfoListItem
|
||||
import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrganism
|
||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||
import io.element.android.libraries.designsystem.background.OnboardingBackground
|
||||
import io.element.android.libraries.designsystem.components.BigIcon
|
||||
import io.element.android.libraries.designsystem.components.ClickableLinkText
|
||||
import io.element.android.libraries.designsystem.components.PageTitle
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.text.buildAnnotatedStringWithStyledPart
|
||||
|
|
@ -89,10 +89,10 @@ private fun AnalyticsOptInHeader(
|
|||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
PageTitle(
|
||||
modifier = Modifier.padding(top = 60.dp, bottom = 12.dp),
|
||||
IconTitleSubtitleMolecule(
|
||||
modifier = Modifier.padding(top = 60.dp, bottom = 28.dp),
|
||||
title = stringResource(id = R.string.screen_analytics_prompt_title, state.applicationName),
|
||||
subtitle = stringResource(id = R.string.screen_analytics_prompt_help_us_improve),
|
||||
subTitle = stringResource(id = R.string.screen_analytics_prompt_help_us_improve),
|
||||
iconStyle = BigIcon.Style.Default(CompoundIcons.Chart())
|
||||
)
|
||||
if (state.hasPolicyLink) {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlinx.serialization.json.Json
|
||||
|
|
@ -84,6 +86,11 @@ class WebViewAudioManager(
|
|||
?.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, "${webView.context.packageName}:ProximitySensorCallWakeLock")
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to ensure that only one coroutine can access the proximity sensor wake lock at a time, preventing re-acquiring or re-releasing it.
|
||||
*/
|
||||
private val proximitySensorMutex = Mutex()
|
||||
|
||||
/**
|
||||
* This listener tracks the current communication device and updates the WebView when it changes.
|
||||
*/
|
||||
|
|
@ -208,8 +215,12 @@ class WebViewAudioManager(
|
|||
return
|
||||
}
|
||||
|
||||
if (proximitySensorWakeLock?.isHeld == true) {
|
||||
proximitySensorWakeLock?.release()
|
||||
coroutineScope.launch {
|
||||
proximitySensorMutex.withLock {
|
||||
if (proximitySensorWakeLock?.isHeld == true) {
|
||||
proximitySensorWakeLock?.release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
audioManager.mode = AudioManager.MODE_NORMAL
|
||||
|
|
@ -397,13 +408,17 @@ class WebViewAudioManager(
|
|||
|
||||
expectedNewCommunicationDeviceId = null
|
||||
|
||||
@Suppress("WakeLock", "WakeLockTimeout")
|
||||
if (device?.type == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE && proximitySensorWakeLock?.isHeld == false) {
|
||||
// If the device is the built-in earpiece, we need to acquire the proximity sensor wake lock
|
||||
proximitySensorWakeLock?.acquire()
|
||||
} else if (proximitySensorWakeLock?.isHeld == true) {
|
||||
// If the device is no longer the earpiece, we need to release the wake lock
|
||||
proximitySensorWakeLock?.release()
|
||||
coroutineScope.launch {
|
||||
proximitySensorMutex.withLock {
|
||||
@Suppress("WakeLock", "WakeLockTimeout")
|
||||
if (device?.type == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE && proximitySensorWakeLock?.isHeld == false) {
|
||||
// If the device is the built-in earpiece, we need to acquire the proximity sensor wake lock
|
||||
proximitySensorWakeLock?.acquire()
|
||||
} else if (proximitySensorWakeLock?.isHeld == true) {
|
||||
// If the device is no longer the earpiece, we need to release the wake lock
|
||||
proximitySensorWakeLock?.release()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="call_foreground_service_channel_title_android">"Käimasolev kõne"</string>
|
||||
<string name="call_foreground_service_channel_title_android">"Pooleliolev 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>
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@
|
|||
<string name="call_foreground_service_channel_title_android">"Połączenie w trakcie"</string>
|
||||
<string name="call_foreground_service_message_android">"Stuknij, aby wrócić do rozmowy"</string>
|
||||
<string name="call_foreground_service_title_android">"☎️ Rozmowa w toku"</string>
|
||||
<string name="call_invalid_audio_device_bluetooth_devices_disabled">"Element Call nie obsługuje korzystania z urządzeń audio Bluetooth w tej wersji Androida. Wybierz inne urządzenie audio."</string>
|
||||
<string name="screen_incoming_call_subtitle_android">"Przychodzące połączenie Element"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.clearAndSetSemantics
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -213,11 +215,19 @@ private fun RoomNameWithAvatar(
|
|||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
val a11yAvatar = stringResource(CommonStrings.a11y_room_avatar)
|
||||
UnsavedAvatar(
|
||||
avatarUri = avatarUri,
|
||||
avatarSize = AvatarSize.EditRoomDetails,
|
||||
avatarType = AvatarType.Room(),
|
||||
modifier = Modifier.clickable(onClick = onAvatarClick),
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
onClick = onAvatarClick,
|
||||
onClickLabel = stringResource(CommonStrings.action_open_context_menu),
|
||||
)
|
||||
.clearAndSetSemantics {
|
||||
contentDescription = a11yAvatar
|
||||
},
|
||||
)
|
||||
|
||||
TextField(
|
||||
|
|
|
|||
|
|
@ -31,10 +31,10 @@ import io.element.android.compound.theme.ElementTheme
|
|||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.ftue.impl.R
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||
import io.element.android.libraries.designsystem.background.OnboardingBackground
|
||||
import io.element.android.libraries.designsystem.components.BigIcon
|
||||
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
|
||||
|
|
@ -59,7 +59,7 @@ fun NotificationsOptInView(
|
|||
.statusBarsPadding()
|
||||
.fillMaxSize(),
|
||||
background = { OnboardingBackground() },
|
||||
header = { NotificationsOptInHeader(modifier = Modifier.padding(top = 60.dp, bottom = 12.dp)) },
|
||||
header = { NotificationsOptInHeader(modifier = Modifier.padding(top = 60.dp, bottom = 28.dp)) },
|
||||
footer = { NotificationsOptInFooter(state) },
|
||||
) {
|
||||
NotificationsOptInContent()
|
||||
|
|
@ -70,10 +70,10 @@ fun NotificationsOptInView(
|
|||
private fun NotificationsOptInHeader(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
PageTitle(
|
||||
IconTitleSubtitleMolecule(
|
||||
modifier = modifier,
|
||||
title = stringResource(R.string.screen_notification_optin_title),
|
||||
subtitle = stringResource(R.string.screen_notification_optin_subtitle),
|
||||
subTitle = stringResource(R.string.screen_notification_optin_subtitle),
|
||||
iconStyle = BigIcon.Style.Default(CompoundIcons.NotificationsSolid()),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@ import io.element.android.compound.theme.ElementTheme
|
|||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.ftue.impl.R
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||
import io.element.android.libraries.designsystem.components.BigIcon
|
||||
import io.element.android.libraries.designsystem.components.PageTitle
|
||||
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
|
||||
|
|
@ -65,10 +65,11 @@ fun ChooseSelfVerificationModeView(
|
|||
)
|
||||
},
|
||||
header = {
|
||||
PageTitle(
|
||||
IconTitleSubtitleMolecule(
|
||||
modifier = Modifier.padding(bottom = 16.dp),
|
||||
iconStyle = BigIcon.Style.Default(CompoundIcons.LockSolid()),
|
||||
title = stringResource(id = R.string.screen_identity_confirmation_title),
|
||||
subtitle = stringResource(id = R.string.screen_identity_confirmation_subtitle)
|
||||
subTitle = stringResource(id = R.string.screen_identity_confirmation_subtitle)
|
||||
)
|
||||
},
|
||||
footer = {
|
||||
|
|
|
|||
|
|
@ -274,13 +274,11 @@ private fun HomeScaffold(
|
|||
floatingActionButton = {
|
||||
if (state.displayActions) {
|
||||
FloatingActionButton(
|
||||
containerColor = ElementTheme.colors.iconPrimary,
|
||||
onClick = onCreateRoomClick
|
||||
onClick = onCreateRoomClick,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = CompoundIcons.Plus(),
|
||||
contentDescription = stringResource(id = R.string.screen_roomlist_a11y_create_message),
|
||||
tint = ElementTheme.colors.iconOnSolidPrimary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
<string name="confirm_recovery_key_banner_title">"Dyw eich allwedd storfa heb ei gydweddu"</string>
|
||||
<string name="full_screen_intent_banner_message">"Er mwyn sicrhau fyddwch chi ddim yn colli galwad bwysig, newidiwch eich gosodiadau i ganiatáu hysbysiadau sgrin lawn pan fydd eich ffôn wedi\'i gloi."</string>
|
||||
<string name="full_screen_intent_banner_title">"Gwella profiad eich galwadau"</string>
|
||||
<string name="screen_home_tab_chats">"Sgyrsiau"</string>
|
||||
<string name="screen_home_tab_spaces">"Gofodau"</string>
|
||||
<string name="screen_invites_decline_chat_message">"Ydych chi\'n siŵr eich bod am wrthod y gwahoddiad i ymuno â %1$s?"</string>
|
||||
<string name="screen_invites_decline_chat_title">"Gwrthod y gwahoddiad"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"Ydych chi\'n siŵr eich bod am wrthod y sgwrs breifat hon gyda %1$s?"</string>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
<string name="confirm_recovery_key_banner_title">"Ihr Schlüsselspeicher ist nicht synchronisiert"</string>
|
||||
<string name="full_screen_intent_banner_message">"Damit Sie keine wichtigen Anrufe verpassen, ändern Sie bitte Ihre Einstellungen, so dass das gesperrte Telefon auch Benachrichtigungen im Vollbildmodus erhalten darf."</string>
|
||||
<string name="full_screen_intent_banner_title">"Verbessere dein Anruferlebnis"</string>
|
||||
<string name="screen_home_tab_chats">"Chats"</string>
|
||||
<string name="screen_home_tab_spaces">"Spaces"</string>
|
||||
<string name="screen_invites_decline_chat_message">"Möchten Sie die Einladung zum Betreten von %1$s wirklich ablehnen?"</string>
|
||||
<string name="screen_invites_decline_chat_title">"Einladung ablehnen"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"Möchten Sie diesen privaten Chat mit %1$s wirklich ablehnen?"</string>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
<string name="confirm_recovery_key_banner_title">"Sinu võtmehoidla pole sünkroonis"</string>
|
||||
<string name="full_screen_intent_banner_message">"Selleks, et sul ainsamgi tähtis kõne ei jääks märkamata, siis palun muuda oma nutiseadme seadistusi nii, et lukustusvaates oleksid täisekraani mõõtu teavitused."</string>
|
||||
<string name="full_screen_intent_banner_title">"Sinu tõhusad telefonikõned"</string>
|
||||
<string name="screen_home_tab_chats">"Vestlused"</string>
|
||||
<string name="screen_home_tab_spaces">"Kogukonnad"</string>
|
||||
<string name="screen_invites_decline_chat_message">"Kas sa oled kindel, et soovid keelduda liitumiskutsest: %1$s?"</string>
|
||||
<string name="screen_invites_decline_chat_title">"Lükka kutse tagasi"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"Kas sa oled kindel, et soovid keelduda privaatsest vestlusest kasutajaga %1$s?"</string>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="banner_battery_optimization_content_android">"Ota tämän sovelluksen akunkäytön optimointi pois käytöstä varmistaaksesi, että kaikki ilmoitukset tulevat perille."</string>
|
||||
<string name="banner_battery_optimization_submit_android">"Ota optimointi pois käytöstä"</string>
|
||||
<string name="banner_set_up_recovery_content">"Palauta kryptografinen identiteettisi ja viestihistoriasi palautusavaimella, mikäli menetät pääsyn kaikkiin laitteisiisi."</string>
|
||||
<string name="banner_set_up_recovery_submit">"Ota palautus käyttöön"</string>
|
||||
<string name="banner_set_up_recovery_title">"Ota palautus käyttöön tilisi suojaamiseksi"</string>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
<string name="confirm_recovery_key_banner_title">"Le stockage de vos clés n’est pas synchronisé"</string>
|
||||
<string name="full_screen_intent_banner_message">"Afin de ne jamais manquer un appel important, veuillez modifier vos paramètres pour autoriser les notifications en plein écran lorsque votre appareil est verrouillé."</string>
|
||||
<string name="full_screen_intent_banner_title">"Améliorez votre expérience d’appel"</string>
|
||||
<string name="screen_home_tab_chats">"Discussions"</string>
|
||||
<string name="screen_home_tab_spaces">"Espaces"</string>
|
||||
<string name="screen_invites_decline_chat_message">"Êtes-vous sûr de vouloir décliner l’invitation à rejoindre %1$s ?"</string>
|
||||
<string name="screen_invites_decline_chat_title">"Refuser l’invitation"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"Êtes-vous sûr de vouloir refuser cette discussion privée avec %1$s ?"</string>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
<string name="confirm_recovery_key_banner_title">"A kulcstároló nincs szinkronizálva"</string>
|
||||
<string name="full_screen_intent_banner_message">"Hogy sose maradjon le egyetlen fontos hívásról sem, a beállításokban engedélyezze a teljes képernyős értesítéseket, amikor a telefon zárolva van."</string>
|
||||
<string name="full_screen_intent_banner_title">"Fokozza a hívásélményét"</string>
|
||||
<string name="screen_home_tab_chats">"Csevegések"</string>
|
||||
<string name="screen_home_tab_spaces">"Terek"</string>
|
||||
<string name="screen_invites_decline_chat_message">"Biztos, hogy elutasítja a meghívást, hogy csatlakozzon ehhez: %1$s?"</string>
|
||||
<string name="screen_invites_decline_chat_title">"Meghívás elutasítása"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"Biztos, hogy elutasítja ezt a privát csevegést vele: %1$s?"</string>
|
||||
|
|
|
|||
|
|
@ -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_home_tab_chats">"Pokalbiai"</string>
|
||||
<string name="screen_home_tab_spaces">"Erdvės"</string>
|
||||
<string name="screen_invites_decline_chat_message">"Ar tikrai norite atmesti kvietimą prisijungti prie %1$s?"</string>
|
||||
<string name="screen_invites_decline_chat_title">"Atmesti kvietimą"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"Ar tikrai norite atmesti šį privatų pokalbį su %1$s ?"</string>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="banner_battery_optimization_content_android">"Wyłącz optymalizację baterii dla tej aplikacji, aby upewnić się, że wszystkie powiadomienia są odbierane."</string>
|
||||
<string name="banner_battery_optimization_submit_android">"Wyłącz optymalizację"</string>
|
||||
<string name="banner_battery_optimization_title_android">"Powiadomienia nie dochodzą?"</string>
|
||||
<string name="banner_set_up_recovery_content">"Wygeneruj nowy klucz przywracania, którego można użyć do przywrócenia historii wiadomości szyfrowanych w przypadku utraty dostępu do swoich urządzeń."</string>
|
||||
<string name="banner_set_up_recovery_submit">"Skonfiguruj przywracanie"</string>
|
||||
<string name="banner_set_up_recovery_title">"Skonfiguruj przywracanie"</string>
|
||||
|
|
@ -9,6 +12,8 @@
|
|||
<string name="confirm_recovery_key_banner_title">"Magazyn kluczy nie jest zsynchronizowany"</string>
|
||||
<string name="full_screen_intent_banner_message">"Upewnij się, że nie pominiesz żadnego połączenia. Zmień swoje ustawienia i zezwól na powiadomienia na blokadzie ekranu."</string>
|
||||
<string name="full_screen_intent_banner_title">"Popraw jakość swoich rozmów"</string>
|
||||
<string name="screen_home_tab_chats">"Czaty"</string>
|
||||
<string name="screen_home_tab_spaces">"Przestrzenie"</string>
|
||||
<string name="screen_invites_decline_chat_message">"Czy na pewno chcesz odrzucić zaproszenie dołączenia do %1$s?"</string>
|
||||
<string name="screen_invites_decline_chat_title">"Odrzuć zaproszenie"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"Czy na pewno chcesz odrzucić rozmowę prywatną z %1$s?"</string>
|
||||
|
|
@ -18,6 +23,7 @@
|
|||
<string name="screen_migration_message">"Jest to jednorazowy proces, dziękujemy za czekanie."</string>
|
||||
<string name="screen_migration_title">"Konfigurowanie Twojego konta."</string>
|
||||
<string name="screen_roomlist_a11y_create_message">"Utwórz nową rozmowę lub pokój"</string>
|
||||
<string name="screen_roomlist_clear_filters">"Wyczyść filtry"</string>
|
||||
<string name="screen_roomlist_empty_message">"Wyślij komuś wiadomość, aby rozpocząć."</string>
|
||||
<string name="screen_roomlist_empty_title">"Brak czatów."</string>
|
||||
<string name="screen_roomlist_filter_favourites">"Ulubione"</string>
|
||||
|
|
@ -40,6 +46,7 @@ Nie masz żadnych nieprzeczytanych wiadomości!"</string>
|
|||
<string name="screen_roomlist_main_space_title">"Wszystkie czaty"</string>
|
||||
<string name="screen_roomlist_mark_as_read">"Oznacz jako przeczytane"</string>
|
||||
<string name="screen_roomlist_mark_as_unread">"Oznacz jako nieprzeczytane"</string>
|
||||
<string name="screen_roomlist_tombstoned_room_description">"Ten pokój został ulepszony"</string>
|
||||
<string name="session_verification_banner_message">"Wygląda na to, że używasz nowego urządzenia. Zweryfikuj się innym urządzeniem, aby uzyskać dostęp do zaszyfrowanych wiadomości."</string>
|
||||
<string name="session_verification_banner_title">"Potwierdź, że to Ty"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@
|
|||
<string name="confirm_recovery_key_banner_title">"Vaše úložisko kľúčov nie je synchronizované"</string>
|
||||
<string name="full_screen_intent_banner_message">"Aby ste už nikdy nezmeškali dôležitý hovor, zmeňte svoje nastavenia a povoľte upozornenia na celú obrazovku, keď je váš telefón uzamknutý."</string>
|
||||
<string name="full_screen_intent_banner_title">"Vylepšite svoj zážitok z hovoru"</string>
|
||||
<string name="screen_home_tab_chats">"Konverzácie"</string>
|
||||
<string name="screen_home_tab_spaces">"Priestory"</string>
|
||||
<string name="screen_invites_decline_chat_message">"Naozaj chcete odmietnuť pozvánku na pripojenie do %1$s?"</string>
|
||||
<string name="screen_invites_decline_chat_title">"Odmietnuť pozvanie"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"Naozaj chcete odmietnuť túto súkromnú konverzáciu s %1$s?"</string>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
<string name="screen_join_room_join_action">"Liitu jututoaga"</string>
|
||||
<string name="screen_join_room_join_restricted_message">"Selle jututoaga liitumiseks sa vajad kutset või pead juba olema kogukonna liige."</string>
|
||||
<string name="screen_join_room_knock_action">"Liitumiseks koputa jututoa uksele"</string>
|
||||
<string name="screen_join_room_knock_message_characters_count">"Lubatud tähemärke: %1$d / %2$d"</string>
|
||||
<string name="screen_join_room_knock_message_description">"Selgitus (kui soovid lisada)"</string>
|
||||
<string name="screen_join_room_knock_sent_description">"Kui sinu liitumispalvega ollakse nõus, siis saad kutse jututoaga liitumiseks."</string>
|
||||
<string name="screen_join_room_knock_sent_title">"Liitumispalve on saadetud"</string>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
<string name="screen_join_room_join_action">"Csatlakozás a szobához"</string>
|
||||
<string name="screen_join_room_join_restricted_message">"A csatlakozáshoz meghívásra vagy tértagságra lehet szüksége."</string>
|
||||
<string name="screen_join_room_knock_action">"Kopogtasson a csatlakozáshoz"</string>
|
||||
<string name="screen_join_room_knock_message_characters_count">"Engedélyezett karakterek: %1$d / %2$d"</string>
|
||||
<string name="screen_join_room_knock_message_description">"Üzenet (nem kötelező)"</string>
|
||||
<string name="screen_join_room_knock_sent_description">"Ha a kérését elfogadják, meghívót kap a szobához való csatlakozáshoz."</string>
|
||||
<string name="screen_join_room_knock_sent_title">"Csatlakozási kérés elküldve"</string>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
<string name="screen_join_room_join_action">"Pripojiť sa do miestnosti"</string>
|
||||
<string name="screen_join_room_join_restricted_message">"Možno budete musieť byť pozvaní alebo byť členom priestoru, aby ste sa mohli pripojiť."</string>
|
||||
<string name="screen_join_room_knock_action">"Zaklopaním sa pripojíte"</string>
|
||||
<string name="screen_join_room_knock_message_characters_count">"Povolené znaky %1$d z %2$d"</string>
|
||||
<string name="screen_join_room_knock_message_description">"Správa (voliteľné)"</string>
|
||||
<string name="screen_join_room_knock_sent_description">"Ak bude vaša žiadosť prijatá, dostanete pozvánku na vstup do miestnosti."</string>
|
||||
<string name="screen_join_room_knock_sent_title">"Žiadosť o pripojenie bola odoslaná"</string>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
<string name="screen_join_room_join_action">"Join room"</string>
|
||||
<string name="screen_join_room_join_restricted_message">"You may need to be invited or be a member of a space in order to join."</string>
|
||||
<string name="screen_join_room_knock_action">"Send request to join"</string>
|
||||
<string name="screen_join_room_knock_message_characters_count">"Allowed characters %1$d of %2$d"</string>
|
||||
<string name="screen_join_room_knock_message_description">"Message (optional)"</string>
|
||||
<string name="screen_join_room_knock_sent_description">"You will receive an invite to join the room if your request is accepted."</string>
|
||||
<string name="screen_join_room_knock_sent_title">"Request to join sent"</string>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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.location.impl.common.ui
|
||||
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.FloatingActionButtonDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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.libraries.designsystem.theme.components.FloatingActionButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
/**
|
||||
* Ref: See design in https://www.figma.com/design/0MMNu7cTOzLOlWb7ctTkv3/Element-X?node-id=3426-141111
|
||||
*/
|
||||
@Composable
|
||||
internal fun LocationFloatingActionButton(
|
||||
isMapCenteredOnUser: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
FloatingActionButton(
|
||||
shape = FloatingActionButtonDefaults.smallShape,
|
||||
containerColor = ElementTheme.colors.bgCanvasDefault,
|
||||
contentColor = ElementTheme.colors.iconPrimary,
|
||||
onClick = onClick,
|
||||
modifier = modifier
|
||||
// Note: design is 40dp, but min is 48 for accessibility.
|
||||
.size(48.dp),
|
||||
) {
|
||||
val iconImage = if (isMapCenteredOnUser) {
|
||||
CompoundIcons.LocationNavigatorCentred()
|
||||
} else {
|
||||
CompoundIcons.LocationNavigator()
|
||||
}
|
||||
Icon(
|
||||
imageVector = iconImage,
|
||||
contentDescription = stringResource(CommonStrings.a11y_move_the_map_to_my_location),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -26,9 +26,6 @@ import io.element.android.libraries.architecture.Presenter
|
|||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
import io.element.android.libraries.matrix.api.room.location.AssetType
|
||||
import io.element.android.libraries.matrix.api.room.message.ReplyParameters
|
||||
import io.element.android.libraries.matrix.api.room.message.replyInThread
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.eventId
|
||||
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -103,17 +100,7 @@ class SendLocationPresenter @Inject constructor(
|
|||
mode: SendLocationState.Mode,
|
||||
) {
|
||||
val replyMode = messageComposerContext.composerMode as? MessageComposerMode.Reply
|
||||
val replyParams = replyMode?.replyToDetails?.let { details ->
|
||||
if (replyMode.inThread) {
|
||||
replyInThread(details.eventId())
|
||||
} else {
|
||||
ReplyParameters(
|
||||
inReplyToEventId = details.eventId(),
|
||||
enforceThreadReply = false,
|
||||
replyWithinThread = false
|
||||
)
|
||||
}
|
||||
}
|
||||
val inReplyToEventId = replyMode?.eventId
|
||||
when (mode) {
|
||||
SendLocationState.Mode.PinLocation -> {
|
||||
val geoUri = event.cameraPosition.toGeoUri()
|
||||
|
|
@ -123,7 +110,7 @@ class SendLocationPresenter @Inject constructor(
|
|||
description = null,
|
||||
zoomLevel = MapDefaults.DEFAULT_ZOOM.toInt(),
|
||||
assetType = AssetType.PIN,
|
||||
replyParameters = replyParams,
|
||||
inReplyToEventId = inReplyToEventId,
|
||||
)
|
||||
analyticsService.capture(
|
||||
Composer(
|
||||
|
|
@ -142,7 +129,7 @@ class SendLocationPresenter @Inject constructor(
|
|||
description = null,
|
||||
zoomLevel = MapDefaults.DEFAULT_ZOOM.toInt(),
|
||||
assetType = AssetType.SENDER,
|
||||
replyParameters = replyParams,
|
||||
inReplyToEventId = inReplyToEventId,
|
||||
)
|
||||
analyticsService.capture(
|
||||
Composer(
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.location.api.Location
|
||||
import io.element.android.features.location.api.internal.centerBottomEdge
|
||||
import io.element.android.features.location.api.internal.rememberTileStyleUrl
|
||||
|
|
@ -38,11 +37,11 @@ import io.element.android.features.location.impl.R
|
|||
import io.element.android.features.location.impl.common.MapDefaults
|
||||
import io.element.android.features.location.impl.common.PermissionDeniedDialog
|
||||
import io.element.android.features.location.impl.common.PermissionRationaleDialog
|
||||
import io.element.android.features.location.impl.common.ui.LocationFloatingActionButton
|
||||
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.components.BottomSheetScaffold
|
||||
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.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
|
|
@ -189,17 +188,13 @@ fun SendLocationView(
|
|||
tint = Color.Unspecified,
|
||||
modifier = Modifier.centerBottomEdge(this),
|
||||
)
|
||||
FloatingActionButton(
|
||||
LocationFloatingActionButton(
|
||||
isMapCenteredOnUser = state.mode == SendLocationState.Mode.SenderLocation,
|
||||
onClick = { state.eventSink(SendLocationEvents.SwitchToMyLocationMode) },
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.padding(end = 16.dp, bottom = 72.dp + navBarPadding),
|
||||
) {
|
||||
when (state.mode) {
|
||||
SendLocationState.Mode.PinLocation -> Icon(imageVector = CompoundIcons.LocationNavigator(), contentDescription = null)
|
||||
SendLocationState.Mode.SenderLocation -> Icon(imageVector = CompoundIcons.LocationNavigatorCentred(), contentDescription = null)
|
||||
}
|
||||
}
|
||||
.padding(end = 18.dp, bottom = 72.dp + navBarPadding),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,10 +27,10 @@ import io.element.android.features.location.api.internal.rememberTileStyleUrl
|
|||
import io.element.android.features.location.impl.common.MapDefaults
|
||||
import io.element.android.features.location.impl.common.PermissionDeniedDialog
|
||||
import io.element.android.features.location.impl.common.PermissionRationaleDialog
|
||||
import io.element.android.features.location.impl.common.ui.LocationFloatingActionButton
|
||||
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.components.FloatingActionButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
|
|
@ -118,14 +118,10 @@ fun ShowLocationView(
|
|||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
FloatingActionButton(
|
||||
LocationFloatingActionButton(
|
||||
isMapCenteredOnUser = state.isTrackMyLocation,
|
||||
onClick = { state.eventSink(ShowLocationEvents.TrackMyLocation(true)) },
|
||||
) {
|
||||
when (state.isTrackMyLocation) {
|
||||
false -> Icon(imageVector = CompoundIcons.LocationNavigator(), contentDescription = null)
|
||||
true -> Icon(imageVector = CompoundIcons.LocationNavigatorCentred(), contentDescription = null)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ import io.element.android.features.location.impl.common.permissions.PermissionsE
|
|||
import io.element.android.features.location.impl.common.permissions.PermissionsPresenter
|
||||
import io.element.android.features.location.impl.common.permissions.PermissionsState
|
||||
import io.element.android.features.messages.test.FakeMessageComposerContext
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
import io.element.android.libraries.matrix.api.room.location.AssetType
|
||||
import io.element.android.libraries.matrix.api.room.message.ReplyParameters
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
|
|
@ -264,7 +264,7 @@ class SendLocationPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `share sender location`() = runTest {
|
||||
val sendLocationResult = lambdaRecorder<String, String, String?, Int?, AssetType?, ReplyParameters?, Result<Unit>> { _, _, _, _, _, _ ->
|
||||
val sendLocationResult = lambdaRecorder<String, String, String?, Int?, AssetType?, EventId?, Result<Unit>> { _, _, _, _, _, _ ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val joinedRoom = FakeJoinedRoom(
|
||||
|
|
@ -328,7 +328,7 @@ class SendLocationPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `share pin location`() = runTest {
|
||||
val sendLocationResult = lambdaRecorder<String, String, String?, Int?, AssetType?, ReplyParameters?, Result<Unit>> { _, _, _, _, _, _ ->
|
||||
val sendLocationResult = lambdaRecorder<String, String, String?, Int?, AssetType?, EventId?, Result<Unit>> { _, _, _, _, _, _ ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val joinedRoom = FakeJoinedRoom(
|
||||
|
|
@ -392,7 +392,7 @@ class SendLocationPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `composer context passes through analytics`() = runTest {
|
||||
val sendLocationResult = lambdaRecorder<String, String, String?, Int?, AssetType?, ReplyParameters?, Result<Unit>> { _, _, _, _, _, _ ->
|
||||
val sendLocationResult = lambdaRecorder<String, String, String?, Int?, AssetType?, EventId?, Result<Unit>> { _, _, _, _, _, _ ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val joinedRoom = FakeJoinedRoom(
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import javax.inject.Inject
|
|||
/**
|
||||
* This class is responsible for managing the login flow, including handling OIDC actions and
|
||||
* submitting login requests.
|
||||
* It's an helper to avoid code duplication. It is used by [OnBoardingPresenter], [ConfirmAccountProviderPresenter]
|
||||
* It's a helper to avoid code duplication. It is used by [OnBoardingPresenter], [ConfirmAccountProviderPresenter]
|
||||
* and [ChooseAccountProviderPresenter].
|
||||
*/
|
||||
class LoginHelper @Inject constructor(
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ Prøv at logge ind manuelt, eller scan QR-koden med en anden enhed."</string>
|
|||
<string name="screen_qr_code_login_start_over_button">"Begynd forfra"</string>
|
||||
<string name="screen_qr_code_login_unknown_error_description">"Der opstod en uventet fejl. Prøv venligst igen."</string>
|
||||
<string name="screen_qr_code_login_verify_code_loading">"Venter på din anden enhed"</string>
|
||||
<string name="screen_qr_code_login_verify_code_subtitle">"Din kontoudbyder kan bede om følgende kode for at bekræfte login."</string>
|
||||
<string name="screen_qr_code_login_verify_code_subtitle">"Din kontoudbyder kan bede om følgende kode for at verificere login\'et."</string>
|
||||
<string name="screen_qr_code_login_verify_code_title">"Din bekræftelseskode"</string>
|
||||
<string name="screen_server_confirmation_change_server">"Skift kontoudbyder"</string>
|
||||
<string name="screen_server_confirmation_message_login_element_dot_io">"En privat server til Element-medarbejdere."</string>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
<string name="screen_change_account_provider_other">"Muu teenusepakkuja"</string>
|
||||
<string name="screen_change_account_provider_subtitle">"Kasuta erinevat teenusepakkujat, milleks võib olla ka sinu oma server või töökoha hallatav server."</string>
|
||||
<string name="screen_change_account_provider_title">"Muuda teenusepakkujat"</string>
|
||||
<string name="screen_change_server_error_element_pro_required_message">"%1$s koduserver eeldab Element Pro rakenduse kasutamist. Palun laadi ta alla rakendustepoest."</string>
|
||||
<string name="screen_change_server_error_element_pro_required_title">"Vajalik on Element Pro"</string>
|
||||
<string name="screen_change_server_error_invalid_homeserver">"Me ei suutnud luuaühendust selle koduserveriga. Palun kontrolli, kas koduserveri aadress on õige. Kui aadress on õige, siis täiendavat teavet oskab sulle anda koduserveri haldaja."</string>
|
||||
<string name="screen_change_server_error_invalid_well_known">"Server pole saadaval vea tõttu well-known failis:
|
||||
%1$s"</string>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
<string name="screen_change_account_provider_other">"Autres"</string>
|
||||
<string name="screen_change_account_provider_subtitle">"Utilisez un autre fournisseur de compte, tel que votre propre serveur privé ou un serveur professionnel."</string>
|
||||
<string name="screen_change_account_provider_title">"Changer de fournisseur de compte"</string>
|
||||
<string name="screen_change_server_error_element_pro_required_message">"L’application Element Pro est requise sur %1$s. Veuillez la télécharger depuis le store."</string>
|
||||
<string name="screen_change_server_error_element_pro_required_title">"Element Pro est requis"</string>
|
||||
<string name="screen_change_server_error_invalid_homeserver">"Nous n’avons pas pu atteindre ce serveur d’accueil. Vérifiez que vous avez correctement saisi l’URL du serveur d’accueil. Si l’URL est correcte, contactez l’administrateur de votre serveur d’accueil pour obtenir de l’aide."</string>
|
||||
<string name="screen_change_server_error_invalid_well_known">"Ce fournisseur de compte n’est pas disponible en raison d’un problème dans le fichier .well-known:
|
||||
%1$s"</string>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
<string name="screen_change_account_provider_other">"Egyéb"</string>
|
||||
<string name="screen_change_account_provider_subtitle">"Másik fiókszolgáltató, például a saját privát kiszolgáló vagy egy munkahelyi fiók használata."</string>
|
||||
<string name="screen_change_account_provider_title">"Fiókszolgáltató módosítása"</string>
|
||||
<string name="screen_change_server_error_element_pro_required_message">"Az Element Pro alkalmazás szükséges a következőn: %1$s. Töltse le az áruházból."</string>
|
||||
<string name="screen_change_server_error_element_pro_required_title">"Element Pro szükséges"</string>
|
||||
<string name="screen_change_server_error_invalid_homeserver">"Nem sikerült elérni ezt a Matrix-kiszolgálót. Ellenőrizze, hogy helyesen adta-e meg a Matrix-kiszolgáló webcímét. Ha a webcím helyes, akkor további segítségért lépjen kapcsolatba a Matrix-kiszolgáló adminisztrátorával."</string>
|
||||
<string name="screen_change_server_error_invalid_well_known">"A kiszolgáló a well-known fájl problémája miatt nem érhető el:
|
||||
%1$s"</string>
|
||||
|
|
@ -34,6 +36,7 @@
|
|||
<string name="screen_login_subtitle">"A Matrix egy nyitott hálózat a biztonságos, decentralizált kommunikációhoz."</string>
|
||||
<string name="screen_login_title">"Örülünk, hogy visszatért!"</string>
|
||||
<string name="screen_login_title_with_homeserver">"Bejelentkezés ide: %1$s"</string>
|
||||
<string name="screen_onboarding_app_version">"Verzió: %1$s"</string>
|
||||
<string name="screen_onboarding_sign_in_manually">"Kézi bejelentkezés"</string>
|
||||
<string name="screen_onboarding_sign_in_to">"Bejelentkezés ide: %1$s"</string>
|
||||
<string name="screen_onboarding_sign_in_with_qr_code">"Bejelentkezés QR-kóddal"</string>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
<string name="screen_change_server_error_invalid_homeserver">"Nepavyko pasiekti šio serverio. Patikrinkite, ar teisingai įvedėte serverio URL. Jei URL yra teisingas, susisiekite su serverio administracija dėl tolimesnės pagalbos."</string>
|
||||
<string name="screen_change_server_form_header">"Serverio URL"</string>
|
||||
<string name="screen_change_server_subtitle">"Koks yra Jūsų serverio adresas?"</string>
|
||||
<string name="screen_create_account_title">"Sukurti paskyrą"</string>
|
||||
<string name="screen_create_account_title">"Kurti paskyrą"</string>
|
||||
<string name="screen_login_error_deactivated_account">"Ši paskyra buvo išjungta."</string>
|
||||
<string name="screen_login_error_invalid_credentials">"Neteisingas vartotojo vardas ir (arba) slaptažodis"</string>
|
||||
<string name="screen_login_error_invalid_user_id">"Tai nėra tinkamas vartotojo vardas. Reikalingas formatas: \'@vartotojas:serveris.org\'"</string>
|
||||
|
|
@ -22,10 +22,13 @@
|
|||
<string name="screen_login_subtitle">"Matrix yra atviras tinklas, skirtas saugiam, decentralizuotam bendravimui."</string>
|
||||
<string name="screen_login_title">"Sveiki sugrįžę!"</string>
|
||||
<string name="screen_login_title_with_homeserver">"Prisijungti prie %1$s"</string>
|
||||
<string name="screen_onboarding_sign_in_manually">"Prisijunkite rankiniu būdu"</string>
|
||||
<string name="screen_onboarding_sign_in_with_qr_code">"Prisijunkite naudodami QR kodą"</string>
|
||||
<string name="screen_onboarding_sign_up">"Sukurti paskyrą"</string>
|
||||
<string name="screen_onboarding_welcome_subtitle">"Sveiki atvykę į %1$s. Įkrautas greitumui ir paprastumui."</string>
|
||||
<string name="screen_onboarding_app_version">"%1$s versija"</string>
|
||||
<string name="screen_onboarding_sign_in_manually">"Prisijungti rankiniu būdu"</string>
|
||||
<string name="screen_onboarding_sign_in_to">"Prisijungti prie %1$s"</string>
|
||||
<string name="screen_onboarding_sign_in_with_qr_code">"Prisijungti su QR kodu"</string>
|
||||
<string name="screen_onboarding_sign_up">"Kurti paskyrą"</string>
|
||||
<string name="screen_onboarding_welcome_message">"Sveiki atvykę į sparčiausią „%1$s“ kada nors. Pagerintas spartai ir paprastumui."</string>
|
||||
<string name="screen_onboarding_welcome_subtitle">"Sveiki atvykę į „%1$s“. Pagerintas spartai ir paprastumui."</string>
|
||||
<string name="screen_onboarding_welcome_title">"Būkite savo elemente"</string>
|
||||
<string name="screen_server_confirmation_change_server">"Keisti paskyros teikėją"</string>
|
||||
<string name="screen_server_confirmation_message_login_element_dot_io">"Privatus serveris “Element” darbuotojams."</string>
|
||||
|
|
|
|||
|
|
@ -13,11 +13,15 @@
|
|||
<string name="screen_change_account_provider_other">"Inne"</string>
|
||||
<string name="screen_change_account_provider_subtitle">"Użyj innego dostawcy konta, takiego jak własny serwer lub konta służbowego."</string>
|
||||
<string name="screen_change_account_provider_title">"Zmień dostawcę konta"</string>
|
||||
<string name="screen_change_server_error_element_pro_required_message">"Wymagana jest aplikacja Element Pro na %1$s. Znajdziesz ją w sklepie z aplikacjami."</string>
|
||||
<string name="screen_change_server_error_element_pro_required_title">"Wymagany jest Element Pro"</string>
|
||||
<string name="screen_change_server_error_invalid_homeserver">"Nie mogliśmy połączyć się z tym serwerem domowym. Sprawdź, czy adres URL serwera został wprowadzony poprawnie. Jeśli adres URL jest poprawny, skontaktuj się z administratorem serwera w celu uzyskania dalszej pomocy."</string>
|
||||
<string name="screen_change_server_error_invalid_well_known">"Serwer nie jest dostępny z powodu problemu pliku .well-known:
|
||||
%1$s"</string>
|
||||
<string name="screen_change_server_error_no_sliding_sync_message">"Wybrany dostawca konta nie wspiera synchronizacji przesuwnej. Wymagana jest aktualizacja serwera %1$s."</string>
|
||||
<string name="screen_change_server_error_unauthorized_homeserver">"%1$s nie posiada zezwolenia na dołączenie do %2$s."</string>
|
||||
<string name="screen_change_server_error_unauthorized_homeserver_content">"Aplikacja została skonfigurowana tak, aby zezwalała na: %1$s."</string>
|
||||
<string name="screen_change_server_error_unauthorized_homeserver_title">"Dostawca konta %1$s jest niedozwolony."</string>
|
||||
<string name="screen_change_server_form_header">"URL serwera domowego"</string>
|
||||
<string name="screen_change_server_form_notice">"Wprowadź adres domeny."</string>
|
||||
<string name="screen_change_server_subtitle">"Jaki jest adres Twojego serwera?"</string>
|
||||
|
|
@ -32,6 +36,7 @@
|
|||
<string name="screen_login_subtitle">"Matrix to otwarta sieć do bezpiecznej i zdecentralizowanej komunikacji."</string>
|
||||
<string name="screen_login_title">"Witaj ponownie!"</string>
|
||||
<string name="screen_login_title_with_homeserver">"Zaloguj się do %1$s"</string>
|
||||
<string name="screen_onboarding_app_version">"Wersja %1$s"</string>
|
||||
<string name="screen_onboarding_sign_in_manually">"Zaloguj się ręcznie"</string>
|
||||
<string name="screen_onboarding_sign_in_to">"Zaloguj się do %1$s"</string>
|
||||
<string name="screen_onboarding_sign_in_with_qr_code">"Zaloguj się za pomocą kodu QR"</string>
|
||||
|
|
@ -87,5 +92,6 @@ Spróbuj zalogować się ręcznie lub zeskanuj kod QR na innym urządzeniu."</st
|
|||
<string name="screen_server_confirmation_message_login_matrix_dot_org">"Matrix to otwarta sieć do bezpiecznej i zdecentralizowanej komunikacji."</string>
|
||||
<string name="screen_server_confirmation_message_register">"Tutaj będą przechowywane Twoje konwersacje - w podobnej formie jak wiadomości widnieją na skrzynce e-mail."</string>
|
||||
<string name="screen_server_confirmation_title_login">"Zamierzasz się zalogować do %1$s"</string>
|
||||
<string name="screen_server_confirmation_title_picker_mode">"Wybierz dostawcę konta"</string>
|
||||
<string name="screen_server_confirmation_title_register">"Zamierzasz utworzyć konto na %1$s"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
<string name="screen_change_account_provider_other">"Iný"</string>
|
||||
<string name="screen_change_account_provider_subtitle">"Použite iného poskytovateľa účtu, ako napríklad vlastný súkromný server alebo pracovný účet."</string>
|
||||
<string name="screen_change_account_provider_title">"Zmeniť poskytovateľa účtu"</string>
|
||||
<string name="screen_change_server_error_element_pro_required_message">"Aplikácia Element Pro je potrebná na %1$s Stiahnite si ju z obchodu."</string>
|
||||
<string name="screen_change_server_error_element_pro_required_title">"Vyžaduje sa Element Pro"</string>
|
||||
<string name="screen_change_server_error_invalid_homeserver">"Nemohli sme sa spojiť s týmto domovským serverom. Skontrolujte prosím, či ste zadali URL adresu domovského servera správne. Ak je adresa URL správna, kontaktujte svoj domovský server pre ďalšiu pomoc."</string>
|
||||
<string name="screen_change_server_error_invalid_well_known">"Server nie je k dispozícii kvôli problému v známom súbore:
|
||||
%1$s"</string>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@
|
|||
<string name="screen_change_account_provider_other">"Other"</string>
|
||||
<string name="screen_change_account_provider_subtitle">"Use a different account provider, such as your own private server or a work account."</string>
|
||||
<string name="screen_change_account_provider_title">"Change account provider"</string>
|
||||
<string name="screen_change_server_error_element_pro_required_message">"The Element Pro app is required on %1$s. Please download it from the store."</string>
|
||||
<string name="screen_change_server_error_element_pro_required_title">"Element Pro required"</string>
|
||||
<string name="screen_change_server_error_invalid_homeserver">"We couldn\'t reach this homeserver. Please check that you have entered the homeserver URL correctly. If the URL is correct, contact your homeserver administrator for further help."</string>
|
||||
<string name="screen_change_server_error_invalid_well_known">"Server isn\'t available due to an issue in the .well-known file:
|
||||
%1$s"</string>
|
||||
|
|
|
|||
|
|
@ -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_signout_confirmation_dialog_content">"Ar tikrai norite atsijungti?"</string>
|
||||
<string name="screen_signout_confirmation_dialog_submit">"Atsijungti"</string>
|
||||
<string name="screen_signout_confirmation_dialog_title">"Atsijungti"</string>
|
||||
<string name="screen_signout_in_progress_dialog_content">"Atsijungiama…"</string>
|
||||
<string name="screen_signout_preference_item">"Atsijungti"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -31,9 +31,9 @@ import io.element.android.libraries.core.extensions.runCatchingExceptions
|
|||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.ProgressCallback
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
|
||||
import io.element.android.libraries.matrix.api.room.message.ReplyParameters
|
||||
import io.element.android.libraries.mediaupload.api.MediaSender
|
||||
import io.element.android.libraries.mediaupload.api.MediaUploadInfo
|
||||
import io.element.android.libraries.mediaupload.api.allFiles
|
||||
|
|
@ -129,14 +129,19 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
|
|||
caption = caption,
|
||||
sendActionState = sendActionState,
|
||||
dismissAfterSend = !useSendQueue,
|
||||
replyParameters = null,
|
||||
inReplyToEventId = null,
|
||||
)
|
||||
|
||||
// Clean up the pre-processed media after it's been sent
|
||||
mediaSender.cleanUp()
|
||||
}
|
||||
}
|
||||
}
|
||||
AttachmentsPreviewEvents.CancelAndDismiss -> {
|
||||
// Cancel media preprocessing and sending
|
||||
preprocessMediaJob?.cancel()
|
||||
// If we couldn't send the pre-processed media, remove it
|
||||
mediaSender.cleanUp()
|
||||
ongoingSendAttachmentJob.value?.cancel()
|
||||
|
||||
// Dismiss the screen
|
||||
|
|
@ -240,7 +245,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
|
|||
caption: String?,
|
||||
sendActionState: MutableState<SendActionState>,
|
||||
dismissAfterSend: Boolean,
|
||||
replyParameters: ReplyParameters?,
|
||||
inReplyToEventId: EventId?,
|
||||
) = runCatchingExceptions {
|
||||
val context = coroutineContext
|
||||
val progressCallback = object : ProgressCallback {
|
||||
|
|
@ -256,7 +261,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
|
|||
caption = caption,
|
||||
formattedCaption = null,
|
||||
progressCallback = progressCallback,
|
||||
replyParameters = replyParameters,
|
||||
inReplyToEventId = inReplyToEventId,
|
||||
).getOrThrow()
|
||||
}.fold(
|
||||
onSuccess = {
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ fun AttachmentsPreviewView(
|
|||
state.eventSink(AttachmentsPreviewEvents.CancelAndClearSendState)
|
||||
}
|
||||
|
||||
BackHandler(enabled = state.sendActionState !is SendActionState.Sending) {
|
||||
BackHandler(enabled = state.sendActionState !is SendActionState.Sending.Uploading && state.sendActionState !is SendActionState.Done) {
|
||||
postCancel()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ import io.element.android.libraries.matrix.api.room.JoinedRoom
|
|||
import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
|
||||
import io.element.android.libraries.matrix.api.room.draft.ComposerDraftType
|
||||
import io.element.android.libraries.matrix.api.room.isDm
|
||||
import io.element.android.libraries.matrix.api.room.message.ReplyParameters
|
||||
import io.element.android.libraries.matrix.api.timeline.TimelineException
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
|
||||
|
|
@ -466,12 +465,7 @@ class MessageComposerPresenter @AssistedInject constructor(
|
|||
body = message.markdown,
|
||||
htmlBody = message.html,
|
||||
intentionalMentions = message.intentionalMentions,
|
||||
replyParameters = ReplyParameters(
|
||||
inReplyToEventId = eventId,
|
||||
enforceThreadReply = inThread,
|
||||
// This should be false until we add a way to make a reply in a thread an explicit reply to the provided eventId
|
||||
replyWithinThread = false,
|
||||
),
|
||||
repliedToEventId = eventId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ internal fun SuggestionsPickerViewPreview() {
|
|||
powerLevel = 0L,
|
||||
normalizedPowerLevel = 0L,
|
||||
isIgnored = false,
|
||||
role = RoomMember.Role.USER,
|
||||
role = RoomMember.Role.User,
|
||||
membershipChangeReason = null,
|
||||
)
|
||||
val anAlias = remember { RoomAlias("#room:domain.org") }
|
||||
|
|
|
|||
|
|
@ -366,7 +366,7 @@ private fun JumpToBottomButton(
|
|||
shape = CircleShape,
|
||||
modifier = Modifier.size(36.dp),
|
||||
containerColor = ElementTheme.colors.bgSubtleSecondary,
|
||||
contentColor = ElementTheme.colors.iconSecondary
|
||||
contentColor = ElementTheme.colors.iconSecondary,
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ package io.element.android.features.messages.impl.timeline.components
|
|||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
|
|
@ -20,7 +21,6 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.BlendMode
|
||||
|
|
@ -42,7 +42,6 @@ import io.element.android.libraries.designsystem.preview.ElementPreview
|
|||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.text.toDp
|
||||
import io.element.android.libraries.designsystem.text.toPx
|
||||
import io.element.android.libraries.designsystem.theme.components.Surface
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.messageFromMeBackground
|
||||
import io.element.android.libraries.designsystem.theme.messageFromOtherBackground
|
||||
|
|
@ -64,7 +63,7 @@ fun MessageEventBubble(
|
|||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit = {},
|
||||
content: @Composable BoxScope.() -> Unit = {},
|
||||
) {
|
||||
fun bubbleShape(): Shape {
|
||||
val topLeftCorner = if (state.cutTopStart) 0.dp else BUBBLE_RADIUS
|
||||
|
|
@ -117,9 +116,12 @@ fun MessageEventBubble(
|
|||
BoxWithConstraints(
|
||||
modifier = modifier
|
||||
.graphicsLayer {
|
||||
shape = bubbleShape
|
||||
clip = true
|
||||
compositingStrategy = CompositingStrategy.Offscreen
|
||||
}
|
||||
.drawWithContent {
|
||||
drawRect(backgroundBubbleColor)
|
||||
drawContent()
|
||||
if (state.cutTopStart) {
|
||||
drawCircle(
|
||||
|
|
@ -137,7 +139,7 @@ fun MessageEventBubble(
|
|||
// when content width is low.
|
||||
contentAlignment = if (state.isMine) Alignment.CenterEnd else Alignment.CenterStart
|
||||
) {
|
||||
Surface(
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.testTag(TestTags.messageBubble)
|
||||
.widthIn(
|
||||
|
|
@ -146,11 +148,8 @@ fun MessageEventBubble(
|
|||
.toInt()
|
||||
.toDp()
|
||||
)
|
||||
.clip(bubbleShape)
|
||||
.then(clickableModifier),
|
||||
color = backgroundBubbleColor,
|
||||
shape = bubbleShape,
|
||||
content = content
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
|||
import io.element.android.libraries.designsystem.text.toAnnotatedString
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.utils.allBooleans
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.tombstone.PredecessorRoom
|
||||
|
||||
|
|
@ -92,7 +91,7 @@ internal fun TimelineItemRoomBeginningViewPreview() = ElementPreview {
|
|||
onPredecessorRoomClick = {},
|
||||
)
|
||||
TimelineItemRoomBeginningView(
|
||||
predecessorRoom = PredecessorRoom(RoomId("!roomId:matrix.org"), EventId("\$eventId:matrix.org")),
|
||||
predecessorRoom = PredecessorRoom(RoomId("!roomId:matrix.org")),
|
||||
roomName = "Room Name",
|
||||
isDm = isDm,
|
||||
onPredecessorRoomClick = {},
|
||||
|
|
|
|||
|
|
@ -35,14 +35,30 @@
|
|||
<string name="screen_room_timeline_less_reactions">"Pokaż mniej"</string>
|
||||
<string name="screen_room_timeline_message_copied">"Skopiowano wiadomość"</string>
|
||||
<string name="screen_room_timeline_no_permission_to_post">"Nie masz uprawnień, aby pisać w tym pokoju"</string>
|
||||
<plurals name="screen_room_timeline_reaction_a11y">
|
||||
<item quantity="one">"%1$d członek zareagował z %2$s"</item>
|
||||
<item quantity="few">"%1$d członków zareagowało z %2$s"</item>
|
||||
<item quantity="many">"%1$d członków zareagowało z %2$s"</item>
|
||||
</plurals>
|
||||
<plurals name="screen_room_timeline_reaction_including_you_a11y">
|
||||
<item quantity="one">"Ty i %1$d członek zareagowaliście z %2$s"</item>
|
||||
<item quantity="few">"Ty i %1$d członków zareagowaliście z %2$s"</item>
|
||||
<item quantity="many">"Ty i %1$d członków zareagowaliście z %2$s"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_timeline_reaction_you_a11y">"Zareagowałeś z %1$s"</string>
|
||||
<string name="screen_room_timeline_reactions_show_less">"Pokaż mniej"</string>
|
||||
<string name="screen_room_timeline_reactions_show_more">"Pokaż więcej"</string>
|
||||
<string name="screen_room_timeline_reactions_show_reactions_summary">"Pokaż podsumowanie reakcji"</string>
|
||||
<string name="screen_room_timeline_read_marker_title">"Nowe"</string>
|
||||
<plurals name="screen_room_timeline_state_changes">
|
||||
<item quantity="one">"%1$d zmiana pokoju"</item>
|
||||
<item quantity="few">"%1$d zmiany pokoju"</item>
|
||||
<item quantity="many">"%1$d zmian pokoju"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_timeline_tombstoned_room_action">"Przejdź do nowego pokoju"</string>
|
||||
<string name="screen_room_timeline_tombstoned_room_message">"Ten pokój został zmieniony i nie jest już aktywny"</string>
|
||||
<string name="screen_room_timeline_upgraded_room_action">"Zobacz stare wiadomości"</string>
|
||||
<string name="screen_room_timeline_upgraded_room_message">"Ten pokój jest kontynuacją innego pokoju"</string>
|
||||
<plurals name="screen_room_typing_many_members">
|
||||
<item quantity="one">"%1$s, %2$s i %3$d inny"</item>
|
||||
<item quantity="few">"%1$s, %2$s i %3$d innych"</item>
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import io.element.android.libraries.androidutils.file.TemporaryUriDeleter
|
|||
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.ProgressCallback
|
||||
import io.element.android.libraries.matrix.api.media.AudioInfo
|
||||
import io.element.android.libraries.matrix.api.media.FileInfo
|
||||
|
|
@ -30,7 +31,6 @@ import io.element.android.libraries.matrix.api.media.ImageInfo
|
|||
import io.element.android.libraries.matrix.api.media.VideoInfo
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
import io.element.android.libraries.matrix.api.room.message.ReplyParameters
|
||||
import io.element.android.libraries.matrix.test.A_CAPTION
|
||||
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
|
||||
import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder
|
||||
|
|
@ -108,7 +108,7 @@ class AttachmentsPreviewPresenterTest {
|
|||
@Test
|
||||
fun `present - send media success scenario`() = runTest {
|
||||
val sendFileResult =
|
||||
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
|
||||
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, EventId?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
|
||||
Result.success(FakeMediaUploadHandler())
|
||||
}
|
||||
val room = FakeJoinedRoom(
|
||||
|
|
@ -123,8 +123,10 @@ class AttachmentsPreviewPresenterTest {
|
|||
},
|
||||
)
|
||||
val onDoneListener = lambdaRecorder<Unit> { }
|
||||
val mediaPreProcessor = FakeMediaPreProcessor()
|
||||
val presenter = createAttachmentsPreviewPresenter(
|
||||
room = room,
|
||||
mediaPreProcessor = mediaPreProcessor,
|
||||
onDoneListener = { onDoneListener() },
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
|
|
@ -143,13 +145,14 @@ class AttachmentsPreviewPresenterTest {
|
|||
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
|
||||
sendFileResult.assertions().isCalledOnce()
|
||||
onDoneListener.assertions().isCalledOnce()
|
||||
assertThat(mediaPreProcessor.cleanUpCallCount).isEqualTo(1)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - send media after pre-processing success scenario`() = runTest {
|
||||
val sendFileResult =
|
||||
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
|
||||
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, EventId?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
|
||||
Result.success(FakeMediaUploadHandler())
|
||||
}
|
||||
val room = FakeJoinedRoom(
|
||||
|
|
@ -159,11 +162,10 @@ class AttachmentsPreviewPresenterTest {
|
|||
)
|
||||
val onDoneListener = lambdaRecorder<Unit> { }
|
||||
val processLatch = CompletableDeferred<Unit>()
|
||||
val mediaPreProcessor = FakeMediaPreProcessor(processLatch)
|
||||
val presenter = createAttachmentsPreviewPresenter(
|
||||
room = room,
|
||||
mediaPreProcessor = FakeMediaPreProcessor(
|
||||
processLatch = processLatch,
|
||||
),
|
||||
mediaPreProcessor = mediaPreProcessor,
|
||||
onDoneListener = { onDoneListener() },
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
|
|
@ -181,13 +183,14 @@ class AttachmentsPreviewPresenterTest {
|
|||
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
|
||||
sendFileResult.assertions().isCalledOnce()
|
||||
onDoneListener.assertions().isCalledOnce()
|
||||
assertThat(mediaPreProcessor.cleanUpCallCount).isEqualTo(1)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - send media before pre-processing success scenario`() = runTest {
|
||||
val sendFileResult =
|
||||
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
|
||||
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, EventId?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
|
||||
Result.success(FakeMediaUploadHandler())
|
||||
}
|
||||
val room = FakeJoinedRoom(
|
||||
|
|
@ -197,11 +200,10 @@ class AttachmentsPreviewPresenterTest {
|
|||
)
|
||||
val onDoneListener = lambdaRecorder<Unit> { }
|
||||
val processLatch = CompletableDeferred<Unit>()
|
||||
val mediaPreProcessor = FakeMediaPreProcessor(processLatch)
|
||||
val presenter = createAttachmentsPreviewPresenter(
|
||||
room = room,
|
||||
mediaPreProcessor = FakeMediaPreProcessor(
|
||||
processLatch = processLatch,
|
||||
),
|
||||
mediaPreProcessor = mediaPreProcessor,
|
||||
onDoneListener = { onDoneListener() },
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
|
|
@ -219,6 +221,7 @@ class AttachmentsPreviewPresenterTest {
|
|||
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
|
||||
sendFileResult.assertions().isCalledOnce()
|
||||
onDoneListener.assertions().isCalledOnce()
|
||||
assertThat(mediaPreProcessor.cleanUpCallCount).isEqualTo(1)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -279,7 +282,9 @@ class AttachmentsPreviewPresenterTest {
|
|||
fun `present - cancel scenario`() = runTest {
|
||||
val onDoneListener = lambdaRecorder<Unit> { }
|
||||
val deleteCallback = lambdaRecorder<Uri?, Unit> {}
|
||||
val mediaPreProcessor = FakeMediaPreProcessor()
|
||||
val presenter = createAttachmentsPreviewPresenter(
|
||||
mediaPreProcessor = mediaPreProcessor,
|
||||
temporaryUriDeleter = FakeTemporaryUriDeleter(deleteCallback),
|
||||
onDoneListener = { onDoneListener() },
|
||||
)
|
||||
|
|
@ -293,13 +298,14 @@ class AttachmentsPreviewPresenterTest {
|
|||
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
|
||||
deleteCallback.assertions().isCalledOnce()
|
||||
onDoneListener.assertions().isCalledOnce()
|
||||
assertThat(mediaPreProcessor.cleanUpCallCount).isEqualTo(1)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - send image with caption success scenario`() = runTest {
|
||||
val sendImageResult =
|
||||
lambdaRecorder { _: File, _: File?, _: ImageInfo, _: String?, _: String?, _: ProgressCallback?, _: ReplyParameters? ->
|
||||
lambdaRecorder { _: File, _: File?, _: ImageInfo, _: String?, _: String?, _: ProgressCallback?, _: EventId? ->
|
||||
Result.success(FakeMediaUploadHandler())
|
||||
}
|
||||
val mediaPreProcessor = FakeMediaPreProcessor().apply {
|
||||
|
|
@ -343,7 +349,7 @@ class AttachmentsPreviewPresenterTest {
|
|||
@Test
|
||||
fun `present - send video with caption success scenario`() = runTest {
|
||||
val sendVideoResult =
|
||||
lambdaRecorder { _: File, _: File?, _: VideoInfo, _: String?, _: String?, _: ProgressCallback?, _: ReplyParameters? ->
|
||||
lambdaRecorder { _: File, _: File?, _: VideoInfo, _: String?, _: String?, _: ProgressCallback?, _: EventId? ->
|
||||
Result.success(FakeMediaUploadHandler())
|
||||
}
|
||||
val mediaPreProcessor = FakeMediaPreProcessor().apply {
|
||||
|
|
@ -387,7 +393,7 @@ class AttachmentsPreviewPresenterTest {
|
|||
@Test
|
||||
fun `present - send audio with caption success scenario`() = runTest {
|
||||
val sendAudioResult =
|
||||
lambdaRecorder<File, AudioInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
|
||||
lambdaRecorder<File, AudioInfo, String?, String?, ProgressCallback?, EventId?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
|
||||
Result.success(FakeMediaUploadHandler())
|
||||
}
|
||||
val mediaPreProcessor = FakeMediaPreProcessor().apply {
|
||||
|
|
@ -429,7 +435,7 @@ class AttachmentsPreviewPresenterTest {
|
|||
fun `present - send media failure scenario without media queue`() = runTest {
|
||||
val failure = MediaPreProcessor.Failure(null)
|
||||
val sendFileResult =
|
||||
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
|
||||
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, EventId?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
|
||||
Result.failure(failure)
|
||||
}
|
||||
val room = FakeJoinedRoom(
|
||||
|
|
@ -460,7 +466,7 @@ class AttachmentsPreviewPresenterTest {
|
|||
fun `present - send media failure scenario with media queue`() = runTest {
|
||||
val failure = MediaPreProcessor.Failure(null)
|
||||
val sendFileResult =
|
||||
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
|
||||
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, EventId?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
|
||||
Result.failure(failure)
|
||||
}
|
||||
val onDoneListenerResult = lambdaRecorder<Unit> {}
|
||||
|
|
|
|||
|
|
@ -48,7 +48,6 @@ import io.element.android.libraries.matrix.api.room.RoomMembersState
|
|||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||
import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
|
||||
import io.element.android.libraries.matrix.api.room.draft.ComposerDraftType
|
||||
import io.element.android.libraries.matrix.api.room.message.ReplyParameters
|
||||
import io.element.android.libraries.matrix.api.timeline.TimelineException
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
|
||||
|
|
@ -610,7 +609,7 @@ class MessageComposerPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - reply message`() = runTest {
|
||||
val replyMessageLambda = lambdaRecorder { _: ReplyParameters, _: String, _: String?, _: List<IntentionalMention>, _: Boolean ->
|
||||
val replyMessageLambda = lambdaRecorder { _: EventId?, _: String, _: String?, _: List<IntentionalMention>, _: Boolean ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val timeline = FakeTimeline().apply {
|
||||
|
|
@ -1100,7 +1099,7 @@ class MessageComposerPresenterTest {
|
|||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Test
|
||||
fun `present - send messages with intentional mentions`() = runTest {
|
||||
val replyMessageLambda = lambdaRecorder { _: ReplyParameters, _: String, _: String?, _: List<IntentionalMention>, _: Boolean ->
|
||||
val replyMessageLambda = lambdaRecorder { _: EventId?, _: String, _: String?, _: List<IntentionalMention>, _: Boolean ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val editMessageLambda = lambdaRecorder { _: EventOrTransactionId, _: String, _: String?, _: List<IntentionalMention> ->
|
||||
|
|
|
|||
|
|
@ -710,11 +710,7 @@ class TimelinePresenterTest {
|
|||
@Test
|
||||
fun `present - timeline room info includes predecessor room when room has predecessor`() = runTest {
|
||||
val predecessorRoomId = RoomId("!predecessor:server.org")
|
||||
val predecessorEventId = EventId("\$predecessorEvent:server.org")
|
||||
val predecessorRoom = PredecessorRoom(
|
||||
roomId = predecessorRoomId,
|
||||
lastEventId = predecessorEventId
|
||||
)
|
||||
val predecessorRoom = PredecessorRoom(roomId = predecessorRoomId)
|
||||
|
||||
val room = FakeJoinedRoom(
|
||||
baseRoom = FakeBaseRoom(
|
||||
|
|
@ -730,7 +726,6 @@ class TimelinePresenterTest {
|
|||
val initialState = awaitFirstItem()
|
||||
assertThat(initialState.timelineRoomInfo.predecessorRoom).isNotNull()
|
||||
assertThat(initialState.timelineRoomInfo.predecessorRoom?.roomId).isEqualTo(predecessorRoomId)
|
||||
assertThat(initialState.timelineRoomInfo.predecessorRoom?.lastEventId).isEqualTo(predecessorEventId)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ import com.google.common.truth.Truth.assertThat
|
|||
import im.vector.app.features.analytics.plan.Composer
|
||||
import io.element.android.features.messages.impl.messagecomposer.aReplyMode
|
||||
import io.element.android.features.messages.test.FakeMessageComposerContext
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.ProgressCallback
|
||||
import io.element.android.libraries.matrix.api.media.AudioInfo
|
||||
import io.element.android.libraries.matrix.api.room.message.ReplyParameters
|
||||
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
|
||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
|
||||
|
|
@ -63,7 +63,7 @@ class VoiceMessageComposerPresenterTest {
|
|||
)
|
||||
private val analyticsService = FakeAnalyticsService()
|
||||
private val sendVoiceMessageResult =
|
||||
lambdaRecorder<File, AudioInfo, List<Float>, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _ ->
|
||||
lambdaRecorder<File, AudioInfo, List<Float>, ProgressCallback?, EventId?, Result<FakeMediaUploadHandler>> { _, _, _, _, _ ->
|
||||
Result.success(FakeMediaUploadHandler())
|
||||
}
|
||||
private val joinedRoom = FakeJoinedRoom(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="a11y_polls_will_remove_selection">"Bydd yn dileu\'r dewis blaenorol"</string>
|
||||
<string name="a11y_polls_winning_answer">"Dyma\'r ateb buddugol"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -4,5 +4,6 @@
|
|||
<item quantity="one">"%1$d Prozent der Stimmen insgesamt"</item>
|
||||
<item quantity="other">"%1$d Prozent der Gesamtstimmen"</item>
|
||||
</plurals>
|
||||
<string name="a11y_polls_will_remove_selection">"Entfernt die vorherige Auswahl"</string>
|
||||
<string name="a11y_polls_winning_answer">"Das ist die Gewinnerantwort"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -4,5 +4,6 @@
|
|||
<item quantity="one">"%1$d protsent kõikidest antud häältest"</item>
|
||||
<item quantity="other">"%1$d protsenti kõikidest antud häältest"</item>
|
||||
</plurals>
|
||||
<string name="a11y_polls_will_remove_selection">"See kustutab eelmise valiku"</string>
|
||||
<string name="a11y_polls_winning_answer">"See vastus võitis"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -4,5 +4,6 @@
|
|||
<item quantity="one">"%1$d pour cent du total des votes"</item>
|
||||
<item quantity="other">"%1$d pour cent du total des votes"</item>
|
||||
</plurals>
|
||||
<string name="a11y_polls_will_remove_selection">"Supprimera la sélection précédente"</string>
|
||||
<string name="a11y_polls_winning_answer">"C’est la réponse gagnante"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -4,5 +4,6 @@
|
|||
<item quantity="one">"az összes szavazat %1$d százaléka"</item>
|
||||
<item quantity="other">"az összes szavazat %1$d százaléka"</item>
|
||||
</plurals>
|
||||
<string name="a11y_polls_will_remove_selection">"Eltávolítja a korábbi kijelölést"</string>
|
||||
<string name="a11y_polls_winning_answer">"Ez a győztes válasz"</string>
|
||||
</resources>
|
||||
|
|
|
|||
10
features/poll/api/src/main/res/values-pl/translations.xml
Normal file
10
features/poll/api/src/main/res/values-pl/translations.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<plurals name="a11y_polls_percent_of_total">
|
||||
<item quantity="one">"%1$d procent wszystkich głosów"</item>
|
||||
<item quantity="few">"%1$d procenty wszystkich głosów"</item>
|
||||
<item quantity="many">"%1$d procent wszystkich głosów"</item>
|
||||
</plurals>
|
||||
<string name="a11y_polls_will_remove_selection">"Spowoduje to usunięcie poprzedniego zaznaczenia"</string>
|
||||
<string name="a11y_polls_winning_answer">"Zwycięska odpowiedź"</string>
|
||||
</resources>
|
||||
|
|
@ -5,5 +5,6 @@
|
|||
<item quantity="few">"%1$d percentá z celkového počtu hlasov"</item>
|
||||
<item quantity="other">"%1$d percent z celkového počtu hlasov"</item>
|
||||
</plurals>
|
||||
<string name="a11y_polls_will_remove_selection">"Odstráni predchádzajúci výber"</string>
|
||||
<string name="a11y_polls_winning_answer">"Toto je víťazná odpoveď"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
<string name="screen_create_poll_anonymous_headline">"Ukryj głosy"</string>
|
||||
<string name="screen_create_poll_answer_hint">"Opcja %1$d"</string>
|
||||
<string name="screen_create_poll_cancel_confirmation_content_android">"Zmiany nie zostały zapisane. Czy na pewno chcesz wrócić?"</string>
|
||||
<string name="screen_create_poll_delete_option_a11y">"Usuń opcję %1$s"</string>
|
||||
<string name="screen_create_poll_question_desc">"Pytanie lub temat"</string>
|
||||
<string name="screen_create_poll_question_hint">"Czego dotyczy ankieta?"</string>
|
||||
<string name="screen_create_poll_title">"Utwórz ankietę"</string>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
<string name="screen_bug_report_error_description_too_short">"Opis jest zbyt krótki, podaj więcej szczegółów na temat tego co się stało. Dzięki!"</string>
|
||||
<string name="screen_bug_report_include_crash_logs">"Wyślij logi awarii"</string>
|
||||
<string name="screen_bug_report_include_logs">"Zezwól na logi"</string>
|
||||
<string name="screen_bug_report_include_logs_error">"Twoje dzienniki są zbyt duże, więc nie można ich uwzględnić w tym raporcie. Prześlij je do nas w inny sposób."</string>
|
||||
<string name="screen_bug_report_include_screenshot">"Wyślij zrzut ekranu"</string>
|
||||
<string name="screen_bug_report_logs_description">"Logi zostaną dołączone do Twojej wiadomości, aby upewnić się, że wszystko działa poprawnie. Aby wysłać wiadomość bez logów, wyłącz to ustawienie."</string>
|
||||
<string name="screen_bug_report_rash_logs_alert_title">"%1$s uległ awarii podczas ostatniego użycia. Czy chcesz przesłać nam raport o awarii?"</string>
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ fun aDmRoomMember(
|
|||
powerLevel: Long = 0,
|
||||
normalizedPowerLevel: Long = powerLevel,
|
||||
isIgnored: Boolean = false,
|
||||
role: RoomMember.Role = RoomMember.Role.USER,
|
||||
role: RoomMember.Role = RoomMember.Role.User,
|
||||
membershipChangeReason: String? = null,
|
||||
) = RoomMember(
|
||||
userId = userId,
|
||||
|
|
|
|||
|
|
@ -13,9 +13,10 @@ import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsV
|
|||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
|
||||
internal fun RoomMember.Role.toAnalyticsMemberRole(): RoomModeration.Role = when (this) {
|
||||
RoomMember.Role.ADMIN -> RoomModeration.Role.Administrator
|
||||
RoomMember.Role.MODERATOR -> RoomModeration.Role.Moderator
|
||||
RoomMember.Role.USER -> RoomModeration.Role.User
|
||||
is RoomMember.Role.Owner -> RoomModeration.Role.Administrator // TODO - distinguish creator from admin
|
||||
RoomMember.Role.Admin -> RoomModeration.Role.Administrator
|
||||
RoomMember.Role.Moderator -> RoomModeration.Role.Moderator
|
||||
RoomMember.Role.User -> RoomModeration.Role.User
|
||||
}
|
||||
|
||||
internal fun analyticsMemberRoleForPowerLevel(powerLevel: Long): RoomModeration.Role {
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@ fun aRoomMember(
|
|||
powerLevel: Long = 0L,
|
||||
normalizedPowerLevel: Long = 0L,
|
||||
isIgnored: Boolean = false,
|
||||
role: RoomMember.Role = RoomMember.Role.USER,
|
||||
role: RoomMember.Role = RoomMember.Role.User,
|
||||
membershipChangeReason: String? = null,
|
||||
) = RoomMember(
|
||||
userId = userId,
|
||||
|
|
@ -178,8 +178,8 @@ fun aRoomMemberList() = persistentListOf(
|
|||
aWalter(),
|
||||
)
|
||||
|
||||
fun anAlice() = aRoomMember(UserId("@alice:server.org"), "Alice", role = RoomMember.Role.ADMIN)
|
||||
fun aBob() = aRoomMember(UserId("@bob:server.org"), "Bob", role = RoomMember.Role.MODERATOR)
|
||||
fun anAlice() = aRoomMember(UserId("@alice:server.org"), "Alice", role = RoomMember.Role.Admin)
|
||||
fun aBob() = aRoomMember(UserId("@bob:server.org"), "Bob", role = RoomMember.Role.Moderator)
|
||||
|
||||
fun aVictor() = aRoomMember(UserId("@victor:server.org"), "Victor", membership = RoomMembershipState.INVITE)
|
||||
|
||||
|
|
|
|||
|
|
@ -293,10 +293,12 @@ private fun RoomMemberListItem(
|
|||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val roleText = when (roomMemberWithIdentity.roomMember.role) {
|
||||
RoomMember.Role.ADMIN -> stringResource(R.string.screen_room_member_list_role_administrator)
|
||||
RoomMember.Role.MODERATOR -> stringResource(R.string.screen_room_member_list_role_moderator)
|
||||
RoomMember.Role.USER -> null
|
||||
val member = roomMemberWithIdentity.roomMember
|
||||
val roleText = when (member.role) {
|
||||
RoomMember.Role.Admin -> stringResource(R.string.screen_room_member_list_role_administrator)
|
||||
RoomMember.Role.Moderator -> stringResource(R.string.screen_room_member_list_role_moderator)
|
||||
is RoomMember.Role.Owner -> stringResource(R.string.screen_room_member_list_role_owner)
|
||||
else -> null
|
||||
}
|
||||
|
||||
MatrixUserRow(
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import io.element.android.anvilannotations.ContributesNode
|
|||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.ui.model.roleOf
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
|
@ -59,7 +60,8 @@ class RolesAndPermissionsNode @AssistedInject constructor(
|
|||
lifecycleScope.launch {
|
||||
room.roomInfoFlow
|
||||
.filter { info ->
|
||||
info.roomPowerLevels?.users?.get(room.sessionId) != RoomMember.Role.ADMIN.powerLevel
|
||||
val role = info.roleOf(room.sessionId)
|
||||
role != RoomMember.Role.Admin && role !is RoomMember.Role.Owner
|
||||
}
|
||||
.take(1)
|
||||
.onEach { navigateUp() }
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import io.element.android.libraries.matrix.api.room.RoomInfo
|
|||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.room.activeRoomMembers
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange
|
||||
import io.element.android.libraries.matrix.ui.model.roleOf
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -50,14 +51,23 @@ class RolesAndPermissionsPresenter @Inject constructor(
|
|||
}
|
||||
val moderatorCount by remember {
|
||||
derivedStateOf {
|
||||
roomInfo.userCountWithRole(activeRoomMemberIds, RoomMember.Role.MODERATOR)
|
||||
roomInfo.userCountWithRole(activeRoomMemberIds, RoomMember.Role.Moderator)
|
||||
}
|
||||
}
|
||||
val adminCount by remember {
|
||||
derivedStateOf {
|
||||
roomInfo.userCountWithRole(activeRoomMemberIds, RoomMember.Role.ADMIN)
|
||||
val admins = roomInfo.userCountWithRole(activeRoomMemberIds, RoomMember.Role.Admin)
|
||||
val ownersCount = if (roomInfo.privilegedCreatorRole) {
|
||||
val superAdmins = roomInfo.userCountWithRole(activeRoomMemberIds, RoomMember.Role.Owner(isCreator = false))
|
||||
val creators = roomInfo.userCountWithRole(activeRoomMemberIds, RoomMember.Role.Owner(isCreator = true))
|
||||
superAdmins + creators
|
||||
} else {
|
||||
0
|
||||
}
|
||||
admins + ownersCount
|
||||
}
|
||||
}
|
||||
val canDemoteSelf = remember { derivedStateOf { roomInfo.roleOf(room.sessionId) !is RoomMember.Role.Owner } }
|
||||
val changeOwnRoleAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
|
||||
val resetPermissionsAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
|
||||
|
||||
|
|
@ -83,8 +93,10 @@ class RolesAndPermissionsPresenter @Inject constructor(
|
|||
}
|
||||
|
||||
return RolesAndPermissionsState(
|
||||
roomSupportsOwnerRole = roomInfo.privilegedCreatorRole,
|
||||
adminCount = adminCount,
|
||||
moderatorCount = moderatorCount,
|
||||
canDemoteSelf = canDemoteSelf.value,
|
||||
changeOwnRoleAction = changeOwnRoleAction.value,
|
||||
resetPermissionsAction = resetPermissionsAction.value,
|
||||
eventSink = { handleEvent(it) },
|
||||
|
|
@ -110,8 +122,6 @@ class RolesAndPermissionsPresenter @Inject constructor(
|
|||
}
|
||||
|
||||
private fun RoomInfo.userCountWithRole(userIds: List<UserId>, role: RoomMember.Role): Int {
|
||||
return this.roomPowerLevels?.users?.count { (userId, level) ->
|
||||
RoomMember.Role.forPowerLevel(level) == role && userId in userIds
|
||||
} ?: 0
|
||||
return usersWithRole(role).filter { it in userIds }.size
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,8 +10,10 @@ package io.element.android.features.roomdetails.impl.rolesandpermissions
|
|||
import io.element.android.libraries.architecture.AsyncAction
|
||||
|
||||
data class RolesAndPermissionsState(
|
||||
val roomSupportsOwnerRole: Boolean,
|
||||
val adminCount: Int,
|
||||
val moderatorCount: Int,
|
||||
val canDemoteSelf: Boolean,
|
||||
val changeOwnRoleAction: AsyncAction<Unit>,
|
||||
val resetPermissionsAction: AsyncAction<Unit>,
|
||||
val eventSink: (RolesAndPermissionsEvents) -> Unit,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import io.element.android.libraries.architecture.AsyncAction
|
|||
class RolesAndPermissionsStateProvider : PreviewParameterProvider<RolesAndPermissionsState> {
|
||||
override val values: Sequence<RolesAndPermissionsState>
|
||||
get() = sequenceOf(
|
||||
aRolesAndPermissionsState(),
|
||||
aRolesAndPermissionsState(roomSupportsOwners = false),
|
||||
aRolesAndPermissionsState(adminCount = 1, moderatorCount = 2),
|
||||
aRolesAndPermissionsState(
|
||||
adminCount = 1,
|
||||
|
|
@ -45,17 +45,22 @@ class RolesAndPermissionsStateProvider : PreviewParameterProvider<RolesAndPermis
|
|||
moderatorCount = 2,
|
||||
resetPermissionsAction = AsyncAction.Failure(IllegalStateException("Failed to reset permissions")),
|
||||
),
|
||||
aRolesAndPermissionsState(canDemoteSelf = false),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun aRolesAndPermissionsState(
|
||||
roomSupportsOwners: Boolean = true,
|
||||
adminCount: Int = 0,
|
||||
moderatorCount: Int = 0,
|
||||
canDemoteSelf: Boolean = true,
|
||||
changeOwnRoleAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
resetPermissionsAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
eventSink: (RolesAndPermissionsEvents) -> Unit = {},
|
||||
) = RolesAndPermissionsState(
|
||||
roomSupportsOwnerRole = roomSupportsOwners,
|
||||
adminCount = adminCount,
|
||||
canDemoteSelf = canDemoteSelf,
|
||||
moderatorCount = moderatorCount,
|
||||
changeOwnRoleAction = changeOwnRoleAction,
|
||||
resetPermissionsAction = resetPermissionsAction,
|
||||
|
|
|
|||
|
|
@ -55,8 +55,14 @@ fun RolesAndPermissionsView(
|
|||
onBackClick = rolesAndPermissionsNavigator::onBackClick,
|
||||
) {
|
||||
ListSectionHeader(title = stringResource(R.string.screen_room_roles_and_permissions_roles_header), hasDivider = false)
|
||||
|
||||
val adminsTitle = if (state.roomSupportsOwnerRole) {
|
||||
stringResource(R.string.screen_room_roles_and_permissions_admins_and_owners)
|
||||
} else {
|
||||
stringResource(R.string.screen_room_roles_and_permissions_admins)
|
||||
}
|
||||
ListItem(
|
||||
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_admins)) },
|
||||
headlineContent = { Text(adminsTitle) },
|
||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Admin())),
|
||||
trailingContent = ListItemContent.Text("${state.adminCount}"),
|
||||
onClick = { rolesAndPermissionsNavigator.openAdminList() },
|
||||
|
|
@ -67,11 +73,13 @@ fun RolesAndPermissionsView(
|
|||
trailingContent = ListItemContent.Text("${state.moderatorCount}"),
|
||||
onClick = { rolesAndPermissionsNavigator.openModeratorList() },
|
||||
)
|
||||
ListItem(
|
||||
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_change_my_role)) },
|
||||
onClick = { state.eventSink(RolesAndPermissionsEvents.ChangeOwnRole) },
|
||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Edit()))
|
||||
)
|
||||
if (state.canDemoteSelf) {
|
||||
ListItem(
|
||||
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_change_my_role)) },
|
||||
onClick = { state.eventSink(RolesAndPermissionsEvents.ChangeOwnRole) },
|
||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Edit()))
|
||||
)
|
||||
}
|
||||
ListSectionHeader(title = stringResource(R.string.screen_room_roles_and_permissions_permissions_header), hasDivider = true)
|
||||
ListItem(
|
||||
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_room_details)) },
|
||||
|
|
@ -170,7 +178,7 @@ private fun ChangeOwnRoleBottomSheet(
|
|||
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_change_role_demote_to_moderator)) },
|
||||
onClick = {
|
||||
sheetState.hide(coroutineScope) {
|
||||
eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.MODERATOR))
|
||||
eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.Moderator))
|
||||
}
|
||||
},
|
||||
style = ListItemStyle.Destructive,
|
||||
|
|
@ -179,7 +187,7 @@ private fun ChangeOwnRoleBottomSheet(
|
|||
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_change_role_demote_to_member)) },
|
||||
onClick = {
|
||||
sheetState.hide(coroutineScope) {
|
||||
eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.USER))
|
||||
eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.User))
|
||||
}
|
||||
},
|
||||
style = ListItemStyle.Destructive,
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ class ChangeRolesNode @AssistedInject constructor(
|
|||
|
||||
private val presenter = presenterFactory.run {
|
||||
val role = when (inputs.listType) {
|
||||
is ListType.Admins -> RoomMember.Role.ADMIN
|
||||
is ListType.Moderators -> RoomMember.Role.MODERATOR
|
||||
is ListType.Admins -> RoomMember.Role.Admin
|
||||
is ListType.Moderators -> RoomMember.Role.Moderator
|
||||
}
|
||||
create(role)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange
|
|||
import io.element.android.libraries.matrix.api.room.powerlevels.usersWithRole
|
||||
import io.element.android.libraries.matrix.api.room.toMatrixUser
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.model.roleOf
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
|
|
@ -74,18 +75,17 @@ class ChangeRolesPresenter @AssistedInject constructor(
|
|||
val exitState: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||
val saveState: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||
val usersWithRole = produceState(initialValue = persistentListOf()) {
|
||||
room.usersWithRole(role)
|
||||
.map { members -> members.map { it.toMatrixUser() } }
|
||||
.onEach { users ->
|
||||
val previous: PersistentList<MatrixUser> = value
|
||||
value = users.toPersistentList()
|
||||
// Users who were selected but didn't have the role, so their role change was pending
|
||||
val toAdd = selectedUsers.value.filter { user -> users.none { it.userId == user.userId } && previous.none { it.userId == user.userId } }
|
||||
// Users who no longer have the role
|
||||
val toRemove = previous.filter { user -> users.none { it.userId == user.userId } }.toSet()
|
||||
selectedUsers.value = (users + toAdd - toRemove).toImmutableList()
|
||||
}
|
||||
.launchIn(this)
|
||||
room.usersWithRole(role).map { members -> members.map { it.toMatrixUser() } }
|
||||
.onEach { users ->
|
||||
val previous: PersistentList<MatrixUser> = value
|
||||
value = users.toPersistentList()
|
||||
// Users who were selected but didn't have the role, so their role change was pending
|
||||
val toAdd = selectedUsers.value.filter { user -> users.none { it.userId == user.userId } && previous.none { it.userId == user.userId } }
|
||||
// Users who no longer have the role
|
||||
val toRemove = previous.filter { user -> users.none { it.userId == user.userId } }.toSet()
|
||||
selectedUsers.value = (users + toAdd - toRemove).toImmutableList()
|
||||
}
|
||||
.launchIn(this)
|
||||
}
|
||||
|
||||
val roomMemberState by room.membersStateFlow.collectAsState()
|
||||
|
|
@ -96,7 +96,6 @@ class ChangeRolesPresenter @AssistedInject constructor(
|
|||
.search(query.orEmpty())
|
||||
.groupedByRole()
|
||||
|
||||
println(results)
|
||||
searchResults = if (results.isEmpty()) {
|
||||
SearchBarResultState.NoResultsFound()
|
||||
} else {
|
||||
|
|
@ -108,9 +107,10 @@ class ChangeRolesPresenter @AssistedInject constructor(
|
|||
|
||||
val roomInfo by room.roomInfoFlow.collectAsState()
|
||||
fun canChangeMemberRole(userId: UserId): Boolean {
|
||||
// An admin can't remove or demote another admin
|
||||
val powerLevel = roomInfo.roomPowerLevels?.users?.get(userId) ?: 0L
|
||||
return RoomMember.Role.forPowerLevel(powerLevel) != RoomMember.Role.ADMIN
|
||||
// This is used to group the
|
||||
val currentUserRole = roomInfo.roleOf(room.sessionId)
|
||||
val otherUserRole = roomInfo.roleOf(userId)
|
||||
return currentUserRole.powerLevel > otherUserRole.powerLevel
|
||||
}
|
||||
|
||||
fun handleEvent(event: ChangeRolesEvent) {
|
||||
|
|
@ -132,11 +132,21 @@ class ChangeRolesPresenter @AssistedInject constructor(
|
|||
selectedUsers.value = newList.toImmutableList()
|
||||
}
|
||||
is ChangeRolesEvent.Save -> {
|
||||
if (role == RoomMember.Role.ADMIN && selectedUsers != usersWithRole && !saveState.value.isConfirming()) {
|
||||
// Confirm adding admin
|
||||
saveState.value = AsyncAction.ConfirmingNoParams
|
||||
} else if (!saveState.value.isLoading()) {
|
||||
coroutineScope.save(usersWithRole.value, selectedUsers, saveState)
|
||||
val currentUserIsAdmin = roomInfo.roleOf(room.sessionId) == RoomMember.Role.Admin
|
||||
val isModifyingAdmins = role == RoomMember.Role.Admin
|
||||
val hasChanges = selectedUsers != usersWithRole
|
||||
val isConfirming = saveState.value.isConfirming()
|
||||
|
||||
val needsConfirmation = currentUserIsAdmin && isModifyingAdmins && hasChanges && !isConfirming
|
||||
|
||||
when {
|
||||
needsConfirmation -> {
|
||||
// Confirm modifying users
|
||||
saveState.value = AsyncAction.ConfirmingNoParams
|
||||
}
|
||||
!saveState.value.isLoading() -> {
|
||||
coroutineScope.save(usersWithRole.value, selectedUsers, saveState)
|
||||
}
|
||||
}
|
||||
}
|
||||
is ChangeRolesEvent.ClearError -> {
|
||||
|
|
@ -174,10 +184,12 @@ class ChangeRolesPresenter @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun List<RoomMember>.groupedByRole(): MembersByRole {
|
||||
val groupedMembers = MembersByRole(this)
|
||||
return MembersByRole(
|
||||
admins = filter { it.role == RoomMember.Role.ADMIN }.sorted(),
|
||||
moderators = filter { it.role == RoomMember.Role.MODERATOR }.sorted(),
|
||||
members = filter { it.role == RoomMember.Role.USER }.sorted(),
|
||||
owners = groupedMembers.owners.sorted(),
|
||||
admins = groupedMembers.admins.sorted(),
|
||||
moderators = groupedMembers.moderators.sorted(),
|
||||
members = groupedMembers.members.sorted(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -202,7 +214,7 @@ class ChangeRolesPresenter @AssistedInject constructor(
|
|||
}
|
||||
for (selectedUser in toRemove) {
|
||||
analyticsService.capture(RoomModeration(RoomModeration.Action.ChangeMemberRole, RoomModeration.Role.User))
|
||||
add(UserRoleChange(selectedUser.userId, RoomMember.Role.USER))
|
||||
add(UserRoleChange(selectedUser.userId, RoomMember.Role.User))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,17 +30,19 @@ data class ChangeRolesState(
|
|||
)
|
||||
|
||||
data class MembersByRole(
|
||||
val owners: ImmutableList<RoomMember>,
|
||||
val admins: ImmutableList<RoomMember>,
|
||||
val moderators: ImmutableList<RoomMember>,
|
||||
val members: ImmutableList<RoomMember>,
|
||||
) {
|
||||
constructor(members: List<RoomMember>) : this(
|
||||
admins = members.filter { it.role == RoomMember.Role.ADMIN }.sorted(),
|
||||
moderators = members.filter { it.role == RoomMember.Role.MODERATOR }.sorted(),
|
||||
members = members.filter { it.role == RoomMember.Role.USER }.sorted(),
|
||||
owners = members.filter { it.role is RoomMember.Role.Owner }.sorted(),
|
||||
admins = members.filter { it.role == RoomMember.Role.Admin }.sorted(),
|
||||
moderators = members.filter { it.role == RoomMember.Role.Moderator }.sorted(),
|
||||
members = members.filter { it.role == RoomMember.Role.User }.sorted(),
|
||||
)
|
||||
|
||||
fun isEmpty() = admins.isEmpty() && moderators.isEmpty() && members.isEmpty()
|
||||
fun isEmpty() = owners.isEmpty() && admins.isEmpty() && moderators.isEmpty() && members.isEmpty()
|
||||
}
|
||||
|
||||
private fun Iterable<RoomMember>.sorted(): ImmutableList<RoomMember> {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
package io.element.android.features.roomdetails.impl.rolesandpermissions.changeroles
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.roomdetails.impl.members.aRoomMember
|
||||
import io.element.android.features.roomdetails.impl.members.aRoomMemberList
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||
|
|
@ -15,6 +16,7 @@ import io.element.android.libraries.matrix.api.core.UserId
|
|||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -24,7 +26,7 @@ class ChangeRolesStateProvider : PreviewParameterProvider<ChangeRolesState> {
|
|||
override val values: Sequence<ChangeRolesState>
|
||||
get() = sequenceOf(
|
||||
aChangeRolesState(),
|
||||
aChangeRolesStateWithSelectedUsers().copy(role = RoomMember.Role.MODERATOR),
|
||||
aChangeRolesStateWithSelectedUsers().copy(role = RoomMember.Role.Moderator),
|
||||
aChangeRolesStateWithSelectedUsers().copy(hasPendingChanges = false),
|
||||
aChangeRolesStateWithSelectedUsers(),
|
||||
aChangeRolesStateWithSelectedUsers().copy(
|
||||
|
|
@ -41,11 +43,12 @@ class ChangeRolesStateProvider : PreviewParameterProvider<ChangeRolesState> {
|
|||
aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.Loading),
|
||||
aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.Success(Unit)),
|
||||
aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.Failure(Exception("boom"))),
|
||||
aChangeRolesStateWithOwners(),
|
||||
)
|
||||
}
|
||||
|
||||
internal fun aChangeRolesState(
|
||||
role: RoomMember.Role = RoomMember.Role.ADMIN,
|
||||
role: RoomMember.Role = RoomMember.Role.Admin,
|
||||
query: String? = null,
|
||||
isSearchActive: Boolean = false,
|
||||
searchResults: SearchBarResultState<MembersByRole> = SearchBarResultState.NoResultsFound(),
|
||||
|
|
@ -84,3 +87,47 @@ internal fun aChangeRolesStateWithSelectedUsers() = aChangeRolesState(
|
|||
hasPendingChanges = true,
|
||||
canRemoveMember = { it != UserId("@alice:server.org") },
|
||||
)
|
||||
|
||||
internal fun aChangeRolesStateWithOwners() = aChangeRolesState(
|
||||
role = RoomMember.Role.Admin,
|
||||
searchResults = SearchBarResultState.Results(
|
||||
MembersByRole(
|
||||
members = persistentListOf(
|
||||
aRoomMember(
|
||||
userId = UserId("@alice:server.org"),
|
||||
displayName = "Alice",
|
||||
role = RoomMember.Role.Owner(isCreator = true),
|
||||
),
|
||||
aRoomMember(
|
||||
userId = UserId("@bob:server.org"),
|
||||
displayName = "Bob",
|
||||
role = RoomMember.Role.Owner(isCreator = false),
|
||||
),
|
||||
aRoomMember(
|
||||
userId = UserId("@carol:server.org"),
|
||||
displayName = "Carol",
|
||||
role = RoomMember.Role.Admin,
|
||||
),
|
||||
aRoomMember(
|
||||
userId = UserId("@david:server.org"),
|
||||
displayName = "David",
|
||||
role = RoomMember.Role.User,
|
||||
),
|
||||
)
|
||||
),
|
||||
),
|
||||
canRemoveMember = { userId ->
|
||||
when (userId) {
|
||||
UserId("@alice:server.org") -> false // Owner - creator
|
||||
UserId("@bob:server.org") -> false // Owner - super admin
|
||||
UserId("@carol:server.org") -> true // Admin
|
||||
UserId("@david:server.org") -> true // User
|
||||
else -> false
|
||||
}
|
||||
},
|
||||
selectedUsers = persistentListOf(
|
||||
aMatrixUser(id = "@alice:server.org", displayName = "Alice"),
|
||||
aMatrixUser(id = "@bob:server.org", displayName = "Bob"),
|
||||
aMatrixUser(id = "@carol:server.org", displayName = "Carol"),
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ 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.Checkbox
|
||||
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchBar
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||
|
|
@ -96,9 +97,9 @@ fun ChangeRolesView(
|
|||
AnimatedVisibility(visible = !state.isSearchActive) {
|
||||
TopAppBar(
|
||||
titleStr = when (state.role) {
|
||||
RoomMember.Role.ADMIN -> stringResource(R.string.screen_room_change_role_administrators_title)
|
||||
RoomMember.Role.MODERATOR -> stringResource(R.string.screen_room_change_role_moderators_title)
|
||||
RoomMember.Role.USER -> error("This should never be reached")
|
||||
RoomMember.Role.Admin -> stringResource(R.string.screen_room_change_role_administrators_title)
|
||||
RoomMember.Role.Moderator -> stringResource(R.string.screen_room_change_role_moderators_title)
|
||||
is RoomMember.Role.Owner, RoomMember.Role.User -> error("This should never be reached")
|
||||
},
|
||||
navigationIcon = {
|
||||
BackButton(onClick = { state.eventSink(ChangeRolesEvent.Exit) })
|
||||
|
|
@ -187,7 +188,7 @@ fun ChangeRolesView(
|
|||
|
||||
when (state.savingState) {
|
||||
is AsyncAction.Confirming -> {
|
||||
if (state.role == RoomMember.Role.ADMIN) {
|
||||
if (state.role == RoomMember.Role.Admin) {
|
||||
// Confirm adding new admins dialogs
|
||||
ConfirmationDialog(
|
||||
title = stringResource(R.string.screen_room_change_role_confirm_add_admin_title),
|
||||
|
|
@ -234,10 +235,30 @@ private fun SearchResultsList(
|
|||
item {
|
||||
selectedUsersList(selectedUsers)
|
||||
}
|
||||
if (searchResults.owners.isNotEmpty()) {
|
||||
stickyHeader { ListSectionHeader(text = stringResource(R.string.screen_room_roles_and_permissions_owners)) }
|
||||
item {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 8.dp),
|
||||
text = stringResource(R.string.screen_room_change_role_moderators_owner_section_footer),
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
style = ElementTheme.typography.fontBodySmRegular,
|
||||
)
|
||||
}
|
||||
items(searchResults.owners, key = { it.userId }) { roomMember ->
|
||||
ListMemberItem(
|
||||
roomMember = roomMember,
|
||||
canRemoveMember = canRemoveMember,
|
||||
onToggleSelection = onToggleSelection,
|
||||
selectedUsers = selectedUsers
|
||||
)
|
||||
}
|
||||
}
|
||||
if (searchResults.admins.isNotEmpty()) {
|
||||
stickyHeader { ListSectionHeader(text = stringResource(R.string.screen_room_roles_and_permissions_admins)) }
|
||||
// Add a footer for the admin section in change role to moderator screen
|
||||
if (currentRole == RoomMember.Role.MODERATOR) {
|
||||
if (currentRole == RoomMember.Role.Moderator) {
|
||||
item {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
|
|
@ -303,20 +324,24 @@ private fun ListMemberItem(
|
|||
) {
|
||||
val canToggle = canRemoveMember(roomMember.userId)
|
||||
val trailingContent: @Composable (() -> Unit) = {
|
||||
Checkbox(
|
||||
checked = selectedUsers.any { it.userId == roomMember.userId },
|
||||
onCheckedChange = { onToggleSelection(roomMember) },
|
||||
enabled = canToggle,
|
||||
)
|
||||
if (canToggle) {
|
||||
Checkbox(
|
||||
checked = selectedUsers.any { it.userId == roomMember.userId },
|
||||
onCheckedChange = { onToggleSelection(roomMember) },
|
||||
)
|
||||
}
|
||||
}
|
||||
Column {
|
||||
MemberRow(
|
||||
modifier = Modifier.clickable(enabled = canToggle, onClick = { onToggleSelection(roomMember) }),
|
||||
avatarData = roomMember.getAvatarData(size = AvatarSize.UserListItem),
|
||||
name = roomMember.getBestName(),
|
||||
userId = roomMember.userId.value.takeIf { roomMember.displayName?.isNotBlank() == true },
|
||||
isPending = roomMember.membership == RoomMembershipState.INVITE,
|
||||
trailingContent = trailingContent,
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
MemberRow(
|
||||
modifier = Modifier.clickable(enabled = canToggle, onClick = { onToggleSelection(roomMember) }),
|
||||
avatarData = roomMember.getAvatarData(size = AvatarSize.UserListItem),
|
||||
name = roomMember.getBestName(),
|
||||
userId = roomMember.userId.value.takeIf { roomMember.displayName?.isNotBlank() == true },
|
||||
isPending = roomMember.membership == RoomMembershipState.INVITE,
|
||||
trailingContent = trailingContent,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -55,15 +55,15 @@ internal fun aChangeRoomPermissionsState(
|
|||
private fun previewPermissions(): RoomPowerLevelsValues {
|
||||
return RoomPowerLevelsValues(
|
||||
// MembershipModeration section
|
||||
invite = RoomMember.Role.ADMIN.powerLevel,
|
||||
kick = RoomMember.Role.MODERATOR.powerLevel,
|
||||
ban = RoomMember.Role.USER.powerLevel,
|
||||
invite = RoomMember.Role.Admin.powerLevel,
|
||||
kick = RoomMember.Role.Moderator.powerLevel,
|
||||
ban = RoomMember.Role.User.powerLevel,
|
||||
// MessagesAndContent section
|
||||
redactEvents = RoomMember.Role.MODERATOR.powerLevel,
|
||||
sendEvents = RoomMember.Role.ADMIN.powerLevel,
|
||||
redactEvents = RoomMember.Role.Moderator.powerLevel,
|
||||
sendEvents = RoomMember.Role.Admin.powerLevel,
|
||||
// RoomDetails section
|
||||
roomName = RoomMember.Role.ADMIN.powerLevel,
|
||||
roomAvatar = RoomMember.Role.MODERATOR.powerLevel,
|
||||
roomTopic = RoomMember.Role.USER.powerLevel,
|
||||
roomName = RoomMember.Role.Admin.powerLevel,
|
||||
roomAvatar = RoomMember.Role.Moderator.powerLevel,
|
||||
roomTopic = RoomMember.Role.User.powerLevel,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,21 +80,21 @@ fun ChangeRoomPermissionsView(
|
|||
ListSectionHeader(titleForSection(item = permissionItem), hasDivider = index > 0)
|
||||
SelectRoleItem(
|
||||
permissionsItem = permissionItem,
|
||||
role = RoomMember.Role.ADMIN,
|
||||
role = RoomMember.Role.Admin,
|
||||
currentPermissions = state.currentPermissions
|
||||
) { item, role ->
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role))
|
||||
}
|
||||
SelectRoleItem(
|
||||
permissionsItem = permissionItem,
|
||||
role = RoomMember.Role.MODERATOR,
|
||||
role = RoomMember.Role.Moderator,
|
||||
currentPermissions = state.currentPermissions
|
||||
) { item, role ->
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role))
|
||||
}
|
||||
SelectRoleItem(
|
||||
permissionsItem = permissionItem,
|
||||
role = RoomMember.Role.USER,
|
||||
role = RoomMember.Role.User,
|
||||
currentPermissions = state.currentPermissions
|
||||
) { item, role ->
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role))
|
||||
|
|
@ -135,9 +135,10 @@ private fun SelectRoleItem(
|
|||
onClick: (RoomPermissionType, RoomMember.Role) -> Unit
|
||||
) {
|
||||
val title = when (role) {
|
||||
RoomMember.Role.ADMIN -> stringResource(R.string.screen_room_change_permissions_administrators)
|
||||
RoomMember.Role.MODERATOR -> stringResource(R.string.screen_room_change_permissions_moderators)
|
||||
RoomMember.Role.USER -> stringResource(R.string.screen_room_change_permissions_everyone)
|
||||
RoomMember.Role.Admin -> stringResource(R.string.screen_room_change_permissions_administrators)
|
||||
RoomMember.Role.Moderator -> stringResource(R.string.screen_room_change_permissions_moderators)
|
||||
RoomMember.Role.User -> stringResource(R.string.screen_room_change_permissions_everyone)
|
||||
else -> error("Unsupported role selected: $role")
|
||||
}
|
||||
ListItem(
|
||||
headlineContent = { Text(text = title) },
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ Wir empfehlen nicht, die Verschlüsselung für Chatrooms die jeder finden und be
|
|||
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Personen können nur beitreten, wenn sie eingeladen werden."</string>
|
||||
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Nur auf Einladung"</string>
|
||||
<string name="screen_security_and_privacy_room_access_section_header">"Chatroomzugang"</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_description">"Räume werden zur Zeit nicht unterstützt."</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_description">"Spaces werden zur Zeit nicht unterstützt."</string>
|
||||
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Spacemitglieder"</string>
|
||||
<string name="screen_security_and_privacy_room_address_section_footer">"Um den Chatroom im Chatroomverzeichnis sichtbar zu machen, benötigen Sie eine Chatroomadresse."</string>
|
||||
<string name="screen_security_and_privacy_room_address_section_header">"Chatroomadresse"</string>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
<string name="screen_room_details_encryption_enabled_subtitle">"Žinutės yra užrakintos. Tik Jūs ir gavėjai turite unikalius raktus joms atrakinti."</string>
|
||||
<string name="screen_room_details_encryption_enabled_title">"Įjungtas žinučių šifravimas"</string>
|
||||
<string name="screen_room_details_invite_people_title">"Pakviesti žmonių"</string>
|
||||
<string name="screen_room_details_leave_conversation_title">"Palikti pokalbį"</string>
|
||||
<string name="screen_room_details_leave_room_title">"Palikti kambarį"</string>
|
||||
<string name="screen_room_details_room_name_label">"Kambario pavadinimas"</string>
|
||||
<string name="screen_room_details_security_title">"Saugumas"</string>
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@
|
|||
<string name="screen_room_member_list_pending_header_title">"Čaká sa"</string>
|
||||
<string name="screen_room_member_list_role_administrator">"Administrátor"</string>
|
||||
<string name="screen_room_member_list_role_moderator">"Moderátor"</string>
|
||||
<string name="screen_room_member_list_role_owner">"Vlastník"</string>
|
||||
<string name="screen_room_member_list_room_members_header_title">"Členovia miestnosti"</string>
|
||||
<string name="screen_room_member_list_unbanning_user">"Zrušenie zákazu %1$s"</string>
|
||||
<string name="screen_room_notification_settings_allow_custom">"Povoliť vlastné nastavenie"</string>
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
<string name="screen_room_change_role_invited_member_name">"%1$s (Pending)"</string>
|
||||
<string name="screen_room_change_role_invited_member_name_android">"(Pending)"</string>
|
||||
<string name="screen_room_change_role_moderators_admin_section_footer">"Admins automatically have moderator privileges"</string>
|
||||
<string name="screen_room_change_role_moderators_owner_section_footer">"Owners automatically have admin privileges."</string>
|
||||
<string name="screen_room_change_role_moderators_title">"Edit Moderators"</string>
|
||||
<string name="screen_room_change_role_section_administrators">"Admins"</string>
|
||||
<string name="screen_room_change_role_section_moderators">"Moderators"</string>
|
||||
|
|
@ -81,6 +82,7 @@
|
|||
<string name="screen_room_member_list_pending_header_title">"Pending"</string>
|
||||
<string name="screen_room_member_list_role_administrator">"Admin"</string>
|
||||
<string name="screen_room_member_list_role_moderator">"Moderator"</string>
|
||||
<string name="screen_room_member_list_role_owner">"Owner"</string>
|
||||
<string name="screen_room_member_list_room_members_header_title">"Room members"</string>
|
||||
<string name="screen_room_member_list_unbanning_user">"Unbanning %1$s"</string>
|
||||
<string name="screen_room_notification_settings_allow_custom">"Allow custom setting"</string>
|
||||
|
|
@ -98,12 +100,14 @@
|
|||
<string name="screen_room_notification_settings_mode_mentions_and_keywords">"Mentions and Keywords only"</string>
|
||||
<string name="screen_room_notification_settings_room_custom_settings_title">"In this room, notify me for"</string>
|
||||
<string name="screen_room_roles_and_permissions_admins">"Admins"</string>
|
||||
<string name="screen_room_roles_and_permissions_admins_and_owners">"Admins and owners"</string>
|
||||
<string name="screen_room_roles_and_permissions_change_my_role">"Change my role"</string>
|
||||
<string name="screen_room_roles_and_permissions_change_role_demote_to_member">"Demote to member"</string>
|
||||
<string name="screen_room_roles_and_permissions_change_role_demote_to_moderator">"Demote to moderator"</string>
|
||||
<string name="screen_room_roles_and_permissions_member_moderation">"Member moderation"</string>
|
||||
<string name="screen_room_roles_and_permissions_messages_and_content">"Messages and content"</string>
|
||||
<string name="screen_room_roles_and_permissions_moderators">"Moderators"</string>
|
||||
<string name="screen_room_roles_and_permissions_owners">"Owners"</string>
|
||||
<string name="screen_room_roles_and_permissions_permissions_header">"Permissions"</string>
|
||||
<string name="screen_room_roles_and_permissions_reset">"Reset permissions"</string>
|
||||
<string name="screen_room_roles_and_permissions_reset_confirm_description">"Once you reset permissions, you will lose the current settings."</string>
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ class RolesAndPermissionPresenterTest {
|
|||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.MODERATOR))
|
||||
initialState.eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.Moderator))
|
||||
|
||||
runCurrent()
|
||||
assertThat(awaitItem().changeOwnRoleAction).isEqualTo(AsyncAction.Loading)
|
||||
|
|
@ -87,7 +87,7 @@ class RolesAndPermissionPresenterTest {
|
|||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.MODERATOR))
|
||||
initialState.eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.Moderator))
|
||||
|
||||
runCurrent()
|
||||
assertThat(awaitItem().changeOwnRoleAction).isEqualTo(AsyncAction.Loading)
|
||||
|
|
|
|||
|
|
@ -47,12 +47,30 @@ class RolesAndPermissionsViewTest {
|
|||
fun `tapping on Admins opens admin list`() {
|
||||
ensureCalledOnce { callback ->
|
||||
rule.setRolesAndPermissionsView(
|
||||
aRolesAndPermissionsState(
|
||||
roomSupportsOwners = false,
|
||||
eventSink = EventsRecorder(expectEvents = false)
|
||||
),
|
||||
openAdminList = callback,
|
||||
)
|
||||
rule.clickOn(R.string.screen_room_roles_and_permissions_admins)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `tapping on Admins and Owners opens admin list`() {
|
||||
ensureCalledOnce { callback ->
|
||||
rule.setRolesAndPermissionsView(
|
||||
aRolesAndPermissionsState(
|
||||
roomSupportsOwners = true,
|
||||
eventSink = EventsRecorder(expectEvents = false)
|
||||
),
|
||||
openAdminList = callback,
|
||||
)
|
||||
rule.clickOn(R.string.screen_room_roles_and_permissions_admins_and_owners)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `tapping on Moderators opens moderators list`() {
|
||||
ensureCalledOnce { callback ->
|
||||
|
|
@ -126,7 +144,7 @@ class RolesAndPermissionsViewTest {
|
|||
)
|
||||
rule.clickOn(R.string.screen_room_roles_and_permissions_change_role_demote_to_moderator)
|
||||
rule.mainClock.advanceTimeBy(1_000L)
|
||||
recorder.assertSingle(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.MODERATOR))
|
||||
recorder.assertSingle(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.Moderator))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -140,7 +158,7 @@ class RolesAndPermissionsViewTest {
|
|||
)
|
||||
rule.clickOn(R.string.screen_room_roles_and_permissions_change_role_demote_to_member)
|
||||
rule.mainClock.advanceTimeBy(1_000L)
|
||||
recorder.assertSingle(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.USER))
|
||||
recorder.assertSingle(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.User))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -160,6 +178,7 @@ class RolesAndPermissionsViewTest {
|
|||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRolesAndPermissionsView(
|
||||
state: RolesAndPermissionsState = aRolesAndPermissionsState(
|
||||
roomSupportsOwners = false,
|
||||
eventSink = EventsRecorder(expectEvents = false),
|
||||
),
|
||||
goBack: () -> Unit = EnsureNeverCalled(),
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import app.cash.molecule.moleculeFlow
|
|||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import im.vector.app.features.analytics.plan.RoomModeration
|
||||
import io.element.android.features.roomdetails.impl.members.aRoomMember
|
||||
import io.element.android.features.roomdetails.impl.members.aRoomMemberList
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
|
|
@ -23,6 +24,7 @@ import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels
|
|||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID_2
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID_3
|
||||
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
|
||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||
|
|
@ -43,7 +45,7 @@ class ChangeRolesPresenterTest {
|
|||
presenter.present()
|
||||
}.test {
|
||||
with(awaitItem()) {
|
||||
assertThat(role).isEqualTo(RoomMember.Role.ADMIN)
|
||||
assertThat(role).isEqualTo(RoomMember.Role.Admin)
|
||||
assertThat(query).isNull()
|
||||
assertThat(isSearchActive).isFalse()
|
||||
assertThat(searchResults).isInstanceOf(SearchBarResultState.Initial::class.java)
|
||||
|
|
@ -70,6 +72,76 @@ class ChangeRolesPresenterTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - canChangeRole of users with lower power level unless they are owners`() = runTest {
|
||||
val creatorUserId = UserId("@creator:matrix.org")
|
||||
val superAdminUserId = UserId("@super_admin:matrix.org")
|
||||
|
||||
val room = FakeJoinedRoom().apply {
|
||||
// User is a creator, so they can change roles of other members. So is `creatorUserId`.
|
||||
givenRoomInfo(
|
||||
aRoomInfo(
|
||||
roomCreators = listOf(sessionId, creatorUserId),
|
||||
roomPowerLevels = RoomPowerLevels(
|
||||
defaultRoomPowerLevelValues(),
|
||||
users = persistentMapOf(
|
||||
// bob is Admin
|
||||
A_USER_ID_2 to RoomMember.Role.Admin.powerLevel,
|
||||
// carol is Moderator
|
||||
A_USER_ID_3 to RoomMember.Role.Moderator.powerLevel,
|
||||
// super_admin is Owner - Superadmin
|
||||
superAdminUserId to RoomMember.Role.Owner(isCreator = false).powerLevel,
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val roomMemberList = aRoomMemberList() + listOf(
|
||||
// Owner - superadmin
|
||||
aRoomMember(userId = superAdminUserId, role = RoomMember.Role.Owner(isCreator = true)),
|
||||
// Owner - creator
|
||||
aRoomMember(userId = creatorUserId, role = RoomMember.Role.Owner(isCreator = true))
|
||||
)
|
||||
givenRoomMembersState(RoomMembersState.Ready(roomMemberList.toPersistentList()))
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
awaitItem().run {
|
||||
assertThat(canChangeMemberRole(A_USER_ID_2)).isTrue() // Admin
|
||||
assertThat(canChangeMemberRole(A_USER_ID_3)).isTrue() // Moderator
|
||||
assertThat(canChangeMemberRole(creatorUserId)).isFalse() // Owner
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when modifying admins, creators are displayed too`() = runTest {
|
||||
val room = FakeJoinedRoom().apply {
|
||||
val creatorUserId = UserId("@creator:matrix.org")
|
||||
val memberList = aRoomMemberList()
|
||||
.plus(aRoomMember(displayName = "CREATOR", role = RoomMember.Role.Owner(isCreator = true), userId = creatorUserId))
|
||||
.toPersistentList()
|
||||
givenRoomInfo(aRoomInfo(roomCreators = listOf(creatorUserId)))
|
||||
givenRoomMembersState(RoomMembersState.Ready(memberList))
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
awaitItem().searchResults.run {
|
||||
assertThat(this).isInstanceOf(SearchBarResultState.Results::class.java)
|
||||
val results = (this as SearchBarResultState.Results).results
|
||||
assertThat(results.admins).isNotEmpty()
|
||||
assertThat(results.owners).isNotEmpty()
|
||||
assertThat(results.owners.last().role).isEqualTo(RoomMember.Role.Owner(isCreator = true))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - ToggleSearchActive changes the value`() = runTest {
|
||||
val room = FakeJoinedRoom().apply {
|
||||
|
|
@ -145,7 +217,7 @@ class ChangeRolesPresenterTest {
|
|||
fun `present - UserSelectionToggle adds and removes users from the selected user list`() = runTest {
|
||||
val room = FakeJoinedRoom().apply {
|
||||
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.ADMIN)))
|
||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin)))
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
|
|
@ -167,7 +239,7 @@ class ChangeRolesPresenterTest {
|
|||
fun `present - hasPendingChanges is true when the initial selected users don't match the new ones`() = runTest {
|
||||
val room = FakeJoinedRoom().apply {
|
||||
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.ADMIN)))
|
||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin)))
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
|
|
@ -196,7 +268,7 @@ class ChangeRolesPresenterTest {
|
|||
fun `present - Exit will display success if no pending changes`() = runTest {
|
||||
val room = FakeJoinedRoom().apply {
|
||||
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.ADMIN)))
|
||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin)))
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
|
|
@ -216,7 +288,7 @@ class ChangeRolesPresenterTest {
|
|||
fun `present - CancelExit will remove exit confirmation`() = runTest {
|
||||
val room = FakeJoinedRoom().apply {
|
||||
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.ADMIN)))
|
||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin)))
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
|
|
@ -242,7 +314,7 @@ class ChangeRolesPresenterTest {
|
|||
fun `present - Exit will display a confirmation dialog if there are pending changes, calling it again will actually exit`() = runTest {
|
||||
val room = FakeJoinedRoom().apply {
|
||||
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.ADMIN)))
|
||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin)))
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
|
|
@ -273,9 +345,9 @@ class ChangeRolesPresenterTest {
|
|||
baseRoom = FakeBaseRoom(updateMembersResult = { Result.success(Unit) }),
|
||||
).apply {
|
||||
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.ADMIN)))
|
||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin)))
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(role = RoomMember.Role.ADMIN, room = room)
|
||||
val presenter = createChangeRolesPresenter(role = RoomMember.Role.Admin, room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -302,9 +374,9 @@ class ChangeRolesPresenterTest {
|
|||
fun `present - CancelSave will remove the confirmation dialog`() = runTest {
|
||||
val room = FakeJoinedRoom().apply {
|
||||
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.ADMIN)))
|
||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin)))
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(role = RoomMember.Role.ADMIN, room = room)
|
||||
val presenter = createChangeRolesPresenter(role = RoomMember.Role.Admin, room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -331,10 +403,10 @@ class ChangeRolesPresenterTest {
|
|||
baseRoom = FakeBaseRoom(updateMembersResult = { Result.success(Unit) }),
|
||||
).apply {
|
||||
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.MODERATOR)))
|
||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Moderator)))
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(
|
||||
role = RoomMember.Role.MODERATOR,
|
||||
role = RoomMember.Role.Moderator,
|
||||
room = room,
|
||||
analyticsService = analyticsService
|
||||
)
|
||||
|
|
@ -358,15 +430,55 @@ class ChangeRolesPresenterTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - Save will just save the changes if the current user is a room creator and the selected users are not`() = runTest {
|
||||
val analyticsService = FakeAnalyticsService()
|
||||
val room = FakeJoinedRoom(
|
||||
updateUserRoleResult = { Result.success(Unit) },
|
||||
baseRoom = FakeBaseRoom(updateMembersResult = { Result.success(Unit) }),
|
||||
).apply {
|
||||
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
||||
givenRoomInfo(
|
||||
aRoomInfo(
|
||||
roomCreators = listOf(sessionId),
|
||||
roomPowerLevels = roomPowerLevelsWithRole(role = RoomMember.Role.Admin, userId = A_USER_ID_2)
|
||||
)
|
||||
)
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(
|
||||
role = RoomMember.Role.Admin,
|
||||
room = room,
|
||||
analyticsService = analyticsService
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.selectedUsers).hasSize(1)
|
||||
|
||||
initialState.eventSink(ChangeRolesEvent.UserSelectionToggled(MatrixUser(A_USER_ID_2)))
|
||||
|
||||
awaitItem().eventSink(ChangeRolesEvent.Save)
|
||||
|
||||
val loadingState = awaitItem()
|
||||
assertThat(loadingState.savingState).isInstanceOf(AsyncAction.Loading::class.java)
|
||||
skipItems(1)
|
||||
|
||||
assertThat(awaitItem().savingState).isEqualTo(AsyncAction.Success(Unit))
|
||||
assertThat(analyticsService.capturedEvents.last()).isEqualTo(RoomModeration(RoomModeration.Action.ChangeMemberRole, RoomModeration.Role.User))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - Save can handle failures and ClearError clears them`() = runTest {
|
||||
val room = FakeJoinedRoom(
|
||||
updateUserRoleResult = { Result.failure(IllegalStateException("Failed")) }
|
||||
).apply {
|
||||
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.MODERATOR)))
|
||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(role = RoomMember.Role.Moderator, userId = A_USER_ID)))
|
||||
}
|
||||
val presenter = createChangeRolesPresenter(role = RoomMember.Role.MODERATOR, room = room)
|
||||
val presenter = createChangeRolesPresenter(role = RoomMember.Role.Moderator, room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -399,7 +511,7 @@ class ChangeRolesPresenterTest {
|
|||
}
|
||||
|
||||
private fun TestScope.createChangeRolesPresenter(
|
||||
role: RoomMember.Role = RoomMember.Role.ADMIN,
|
||||
role: RoomMember.Role = RoomMember.Role.Admin,
|
||||
room: FakeJoinedRoom = FakeJoinedRoom(),
|
||||
dispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
|
||||
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
|
||||
|
|
|
|||
|
|
@ -41,11 +41,25 @@ class ChangeRolesViewTest {
|
|||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
@Test
|
||||
fun `passing a 'USER' role throws an exception`() {
|
||||
fun `passing a 'User' role throws an exception`() {
|
||||
val exception = runCatchingExceptions {
|
||||
rule.setChangeRolesContent(
|
||||
state = aChangeRolesState(
|
||||
role = RoomMember.Role.USER,
|
||||
role = RoomMember.Role.User,
|
||||
eventSink = EnsureNeverCalledWithParam(),
|
||||
),
|
||||
)
|
||||
}.exceptionOrNull()
|
||||
|
||||
assertThat(exception).isNotNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `passing an 'Owner' role throws an exception`() {
|
||||
val exception = runCatchingExceptions {
|
||||
rule.setChangeRolesContent(
|
||||
state = aChangeRolesState(
|
||||
role = RoomMember.Role.Owner(isCreator = true),
|
||||
eventSink = EnsureNeverCalledWithParam(),
|
||||
),
|
||||
)
|
||||
|
|
@ -166,7 +180,7 @@ class ChangeRolesViewTest {
|
|||
val eventsRecorder = EventsRecorder<ChangeRolesEvent>()
|
||||
rule.setChangeRolesContent(
|
||||
state = aChangeRolesState(
|
||||
role = RoomMember.Role.ADMIN,
|
||||
role = RoomMember.Role.Admin,
|
||||
isSearchActive = true,
|
||||
savingState = AsyncAction.ConfirmingNoParams,
|
||||
eventSink = eventsRecorder,
|
||||
|
|
@ -183,7 +197,7 @@ class ChangeRolesViewTest {
|
|||
val eventsRecorder = EventsRecorder<ChangeRolesEvent>()
|
||||
rule.setChangeRolesContent(
|
||||
state = aChangeRolesState(
|
||||
role = RoomMember.Role.ADMIN,
|
||||
role = RoomMember.Role.Admin,
|
||||
isSearchActive = true,
|
||||
savingState = AsyncAction.ConfirmingNoParams,
|
||||
eventSink = eventsRecorder,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import io.element.android.libraries.matrix.test.A_USER_ID_2
|
|||
import io.element.android.libraries.matrix.test.A_USER_ID_3
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID_4
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID_5
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID_6
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID_7
|
||||
import io.element.android.libraries.matrix.test.room.aRoomMember
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import org.junit.Test
|
||||
|
|
@ -22,22 +24,28 @@ class MembersByRoleTest {
|
|||
@Test
|
||||
fun `constructor - with single member list categorizes and sorts members`() {
|
||||
val members = listOf(
|
||||
aRoomMember(A_USER_ID_2, displayName = "Bob", role = RoomMember.Role.ADMIN),
|
||||
aRoomMember(A_USER_ID, displayName = "Alice", role = RoomMember.Role.ADMIN),
|
||||
aRoomMember(A_USER_ID_3, displayName = "Carol", role = RoomMember.Role.USER),
|
||||
aRoomMember(A_USER_ID_5, displayName = "Eve", role = RoomMember.Role.USER),
|
||||
aRoomMember(A_USER_ID_4, displayName = "David", role = RoomMember.Role.USER),
|
||||
aRoomMember(A_USER_ID_2, displayName = "Bob", role = RoomMember.Role.Admin),
|
||||
aRoomMember(A_USER_ID, displayName = "Alice", role = RoomMember.Role.Admin),
|
||||
aRoomMember(A_USER_ID_3, displayName = "Carol", role = RoomMember.Role.User),
|
||||
aRoomMember(A_USER_ID_5, displayName = "Eve", role = RoomMember.Role.User),
|
||||
aRoomMember(A_USER_ID_4, displayName = "David", role = RoomMember.Role.User),
|
||||
aRoomMember(A_USER_ID_6, displayName = "Justin", role = RoomMember.Role.Owner(isCreator = true)),
|
||||
aRoomMember(A_USER_ID_7, displayName = "Mallory", role = RoomMember.Role.Owner(isCreator = false)),
|
||||
)
|
||||
val membersByRole = MembersByRole(members = members)
|
||||
assertThat(membersByRole.owners).containsExactly(
|
||||
aRoomMember(A_USER_ID_6, displayName = "Justin", role = RoomMember.Role.Owner(isCreator = true)),
|
||||
aRoomMember(A_USER_ID_7, displayName = "Mallory", role = RoomMember.Role.Owner(isCreator = false)),
|
||||
)
|
||||
assertThat(membersByRole.admins).containsExactly(
|
||||
aRoomMember(A_USER_ID, displayName = "Alice", role = RoomMember.Role.ADMIN),
|
||||
aRoomMember(A_USER_ID_2, displayName = "Bob", role = RoomMember.Role.ADMIN),
|
||||
aRoomMember(A_USER_ID, displayName = "Alice", role = RoomMember.Role.Admin),
|
||||
aRoomMember(A_USER_ID_2, displayName = "Bob", role = RoomMember.Role.Admin),
|
||||
)
|
||||
assertThat(membersByRole.moderators).isEmpty()
|
||||
assertThat(membersByRole.members).containsExactly(
|
||||
aRoomMember(A_USER_ID_3, displayName = "Carol", role = RoomMember.Role.USER),
|
||||
aRoomMember(A_USER_ID_4, displayName = "David", role = RoomMember.Role.USER),
|
||||
aRoomMember(A_USER_ID_5, displayName = "Eve", role = RoomMember.Role.USER),
|
||||
aRoomMember(A_USER_ID_3, displayName = "Carol", role = RoomMember.Role.User),
|
||||
aRoomMember(A_USER_ID_4, displayName = "David", role = RoomMember.Role.User),
|
||||
aRoomMember(A_USER_ID_5, displayName = "Eve", role = RoomMember.Role.User),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -46,24 +54,35 @@ class MembersByRoleTest {
|
|||
val emptyMembersByRole = MembersByRole(emptyList())
|
||||
assertThat(emptyMembersByRole.isEmpty()).isTrue()
|
||||
|
||||
val membersByRoleWithOwners = MembersByRole(
|
||||
owners = persistentListOf(aRoomMember(A_USER_ID, role = RoomMember.Role.Admin)),
|
||||
admins = persistentListOf(),
|
||||
moderators = persistentListOf(),
|
||||
members = persistentListOf(),
|
||||
)
|
||||
assertThat(membersByRoleWithOwners.isEmpty()).isFalse()
|
||||
|
||||
val membersByRoleWithAdmins = MembersByRole(
|
||||
admins = persistentListOf(aRoomMember(A_USER_ID, role = RoomMember.Role.ADMIN)),
|
||||
owners = persistentListOf(),
|
||||
admins = persistentListOf(aRoomMember(A_USER_ID, role = RoomMember.Role.Admin)),
|
||||
moderators = persistentListOf(),
|
||||
members = persistentListOf(),
|
||||
)
|
||||
assertThat(membersByRoleWithAdmins.isEmpty()).isFalse()
|
||||
|
||||
val membersByRoleWithModerators = MembersByRole(
|
||||
owners = persistentListOf(),
|
||||
admins = persistentListOf(),
|
||||
moderators = persistentListOf(aRoomMember(A_USER_ID, role = RoomMember.Role.MODERATOR)),
|
||||
moderators = persistentListOf(aRoomMember(A_USER_ID, role = RoomMember.Role.Moderator)),
|
||||
members = persistentListOf(),
|
||||
)
|
||||
assertThat(membersByRoleWithModerators.isEmpty()).isFalse()
|
||||
|
||||
val membersByRoleWithMembers = MembersByRole(
|
||||
owners = persistentListOf(),
|
||||
admins = persistentListOf(),
|
||||
moderators = persistentListOf(),
|
||||
members = persistentListOf(aRoomMember(A_USER_ID, role = RoomMember.Role.USER)),
|
||||
members = persistentListOf(aRoomMember(A_USER_ID, role = RoomMember.Role.User)),
|
||||
)
|
||||
assertThat(membersByRoleWithMembers.isEmpty()).isFalse()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ import app.cash.turbine.test
|
|||
import com.google.common.truth.Truth.assertThat
|
||||
import im.vector.app.features.analytics.plan.RoomModeration
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember.Role.ADMIN
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember.Role.MODERATOR
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember.Role.USER
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember.Role.Admin
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember.Role.Moderator
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember.Role.User
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
|
||||
import io.element.android.libraries.matrix.test.room.FakeBaseRoom
|
||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||
|
|
@ -100,13 +100,13 @@ class ChangeBaseRoomPermissionsPresenterTest {
|
|||
presenter.present()
|
||||
}.test {
|
||||
val state = awaitUpdatedItem()
|
||||
assertThat(state.currentPermissions?.roomName).isEqualTo(ADMIN.powerLevel)
|
||||
assertThat(state.currentPermissions?.roomName).isEqualTo(Admin.powerLevel)
|
||||
assertThat(state.hasChanges).isFalse()
|
||||
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, MODERATOR))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator))
|
||||
|
||||
awaitItem().run {
|
||||
assertThat(currentPermissions?.roomName).isEqualTo(MODERATOR.powerLevel)
|
||||
assertThat(currentPermissions?.roomName).isEqualTo(Moderator.powerLevel)
|
||||
assertThat(hasChanges).isTrue()
|
||||
}
|
||||
}
|
||||
|
|
@ -120,28 +120,28 @@ class ChangeBaseRoomPermissionsPresenterTest {
|
|||
}.test {
|
||||
val state = awaitUpdatedItem()
|
||||
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, MODERATOR))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, MODERATOR))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, MODERATOR))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, MODERATOR))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, MODERATOR))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, MODERATOR))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, MODERATOR))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, MODERATOR))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, Moderator))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, Moderator))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, Moderator))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, Moderator))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, Moderator))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, Moderator))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, Moderator))
|
||||
|
||||
val items = cancelAndConsumeRemainingEvents()
|
||||
|
||||
(items.last() as? Event.Item<ChangeRoomPermissionsState>)?.value?.run {
|
||||
assertThat(currentPermissions).isEqualTo(
|
||||
RoomPowerLevelsValues(
|
||||
invite = MODERATOR.powerLevel,
|
||||
kick = MODERATOR.powerLevel,
|
||||
ban = MODERATOR.powerLevel,
|
||||
redactEvents = MODERATOR.powerLevel,
|
||||
sendEvents = MODERATOR.powerLevel,
|
||||
roomName = MODERATOR.powerLevel,
|
||||
roomAvatar = MODERATOR.powerLevel,
|
||||
roomTopic = MODERATOR.powerLevel,
|
||||
invite = Moderator.powerLevel,
|
||||
kick = Moderator.powerLevel,
|
||||
ban = Moderator.powerLevel,
|
||||
redactEvents = Moderator.powerLevel,
|
||||
sendEvents = Moderator.powerLevel,
|
||||
roomName = Moderator.powerLevel,
|
||||
roomAvatar = Moderator.powerLevel,
|
||||
roomTopic = Moderator.powerLevel,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
@ -162,17 +162,17 @@ class ChangeBaseRoomPermissionsPresenterTest {
|
|||
presenter.present()
|
||||
}.test {
|
||||
val state = awaitUpdatedItem()
|
||||
assertThat(state.currentPermissions?.roomName).isEqualTo(ADMIN.powerLevel)
|
||||
assertThat(state.currentPermissions?.roomName).isEqualTo(Admin.powerLevel)
|
||||
assertThat(state.hasChanges).isFalse()
|
||||
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, MODERATOR))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, MODERATOR))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, MODERATOR))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, MODERATOR))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, USER))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, ADMIN))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, ADMIN))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, ADMIN))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, Moderator))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, Moderator))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, Moderator))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, User))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, Admin))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, Admin))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, Admin))
|
||||
skipItems(7)
|
||||
assertThat(awaitItem().hasChanges).isTrue()
|
||||
|
||||
|
|
@ -181,7 +181,7 @@ class ChangeBaseRoomPermissionsPresenterTest {
|
|||
assertThat(awaitItem().saveAction).isEqualTo(AsyncAction.Loading)
|
||||
assertThat(awaitItem().hasChanges).isFalse()
|
||||
awaitItem().run {
|
||||
assertThat(currentPermissions?.roomName).isEqualTo(MODERATOR.powerLevel)
|
||||
assertThat(currentPermissions?.roomName).isEqualTo(Moderator.powerLevel)
|
||||
assertThat(saveAction).isEqualTo(AsyncAction.Success(Unit))
|
||||
}
|
||||
assertThat(analyticsService.capturedEvents).containsExactlyElementsIn(
|
||||
|
|
@ -227,17 +227,17 @@ class ChangeBaseRoomPermissionsPresenterTest {
|
|||
presenter.present()
|
||||
}.test {
|
||||
val state = awaitUpdatedItem()
|
||||
assertThat(state.currentPermissions?.roomName).isEqualTo(ADMIN.powerLevel)
|
||||
assertThat(state.currentPermissions?.roomName).isEqualTo(Admin.powerLevel)
|
||||
assertThat(state.hasChanges).isFalse()
|
||||
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, MODERATOR))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator))
|
||||
assertThat(awaitItem().hasChanges).isTrue()
|
||||
|
||||
state.eventSink(ChangeRoomPermissionsEvent.Save)
|
||||
|
||||
assertThat(awaitItem().saveAction).isEqualTo(AsyncAction.Loading)
|
||||
awaitItem().run {
|
||||
assertThat(currentPermissions?.roomName).isEqualTo(MODERATOR.powerLevel)
|
||||
assertThat(currentPermissions?.roomName).isEqualTo(Moderator.powerLevel)
|
||||
// Couldn't save the changes, so they're still pending
|
||||
assertThat(hasChanges).isTrue()
|
||||
assertThat(saveAction).isInstanceOf(AsyncAction.Failure::class.java)
|
||||
|
|
@ -245,7 +245,7 @@ class ChangeBaseRoomPermissionsPresenterTest {
|
|||
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ResetPendingActions)
|
||||
awaitItem().run {
|
||||
assertThat(currentPermissions?.roomName).isEqualTo(MODERATOR.powerLevel)
|
||||
assertThat(currentPermissions?.roomName).isEqualTo(Moderator.powerLevel)
|
||||
assertThat(saveAction).isEqualTo(AsyncAction.Uninitialized)
|
||||
assertThat(hasChanges).isTrue()
|
||||
}
|
||||
|
|
@ -259,7 +259,7 @@ class ChangeBaseRoomPermissionsPresenterTest {
|
|||
presenter.present()
|
||||
}.test {
|
||||
val state = awaitUpdatedItem()
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, MODERATOR))
|
||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator))
|
||||
assertThat(awaitItem().hasChanges).isTrue()
|
||||
|
||||
state.eventSink(ChangeRoomPermissionsEvent.Exit)
|
||||
|
|
|
|||
|
|
@ -115,9 +115,9 @@ class ChangeBaseRoomPermissionsViewTest {
|
|||
rule.onAllNodesWithText(users).onFirst().performClick()
|
||||
recorder.assertList(
|
||||
listOf(
|
||||
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.ADMIN),
|
||||
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.MODERATOR),
|
||||
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.USER),
|
||||
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.Admin),
|
||||
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.Moderator),
|
||||
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.User),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ data class RoomDescription(
|
|||
enum class JoinRule {
|
||||
PUBLIC,
|
||||
KNOCK,
|
||||
RESTRICTED,
|
||||
KNOCK_RESTRICTED,
|
||||
INVITE,
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ fun MatrixRoomDescription.toFeatureModel(): RoomDescription {
|
|||
joinRule = when (joinRule) {
|
||||
MatrixRoomDescription.JoinRule.PUBLIC -> RoomDescription.JoinRule.PUBLIC
|
||||
MatrixRoomDescription.JoinRule.KNOCK -> RoomDescription.JoinRule.KNOCK
|
||||
MatrixRoomDescription.JoinRule.RESTRICTED -> RoomDescription.JoinRule.RESTRICTED
|
||||
MatrixRoomDescription.JoinRule.KNOCK_RESTRICTED -> RoomDescription.JoinRule.KNOCK_RESTRICTED
|
||||
MatrixRoomDescription.JoinRule.INVITE -> RoomDescription.JoinRule.INVITE
|
||||
MatrixRoomDescription.JoinRule.UNKNOWN -> RoomDescription.JoinRule.UNKNOWN
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,4 +12,9 @@
|
|||
<string name="screen_bottom_sheet_manage_room_member_remove">"Usuń z pokoju"</string>
|
||||
<string name="screen_bottom_sheet_manage_room_member_remove_confirmation_title">"Usunąć członka i zablokować możliwość dołączenia w przyszłości?"</string>
|
||||
<string name="screen_bottom_sheet_manage_room_member_removing_user">"Usuwanie %1$s…"</string>
|
||||
<string name="screen_bottom_sheet_manage_room_member_unban">"Odbanuj z pokoju"</string>
|
||||
<string name="screen_bottom_sheet_manage_room_member_unban_member_confirmation_action">"Odbanuj"</string>
|
||||
<string name="screen_bottom_sheet_manage_room_member_unban_member_confirmation_description">"Mogą ponownie dołączyć do pokoju, po otrzymaniu zaproszenia"</string>
|
||||
<string name="screen_bottom_sheet_manage_room_member_unban_member_confirmation_title">"Czy na pewno chcesz odbanować tego członka?"</string>
|
||||
<string name="screen_bottom_sheet_manage_room_member_unbanning_user">"Odbanowuję %1$s"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -61,8 +61,8 @@ class RoomMemberModerationPresenterTest {
|
|||
val room = aJoinedRoom(
|
||||
canBan = false,
|
||||
canKick = false,
|
||||
myUserRole = RoomMember.Role.USER,
|
||||
targetRoomMember = aRoomMember(userId = A_USER_ID, powerLevel = RoomMember.Role.USER.powerLevel)
|
||||
myUserRole = RoomMember.Role.User,
|
||||
targetRoomMember = aRoomMember(userId = A_USER_ID, powerLevel = RoomMember.Role.User.powerLevel)
|
||||
)
|
||||
createRoomMemberModerationPresenter(room = room).test {
|
||||
val initialState = awaitState()
|
||||
|
|
@ -81,7 +81,7 @@ class RoomMemberModerationPresenterTest {
|
|||
val room = aJoinedRoom(
|
||||
canBan = true,
|
||||
canKick = true,
|
||||
myUserRole = RoomMember.Role.ADMIN,
|
||||
myUserRole = RoomMember.Role.Admin,
|
||||
targetRoomMember = null
|
||||
)
|
||||
createRoomMemberModerationPresenter(room = room).test {
|
||||
|
|
@ -103,8 +103,8 @@ class RoomMemberModerationPresenterTest {
|
|||
val room = aJoinedRoom(
|
||||
canBan = true,
|
||||
canKick = true,
|
||||
myUserRole = RoomMember.Role.ADMIN,
|
||||
targetRoomMember = aRoomMember(userId = A_USER_ID, powerLevel = RoomMember.Role.USER.powerLevel)
|
||||
myUserRole = RoomMember.Role.Admin,
|
||||
targetRoomMember = aRoomMember(userId = A_USER_ID, powerLevel = RoomMember.Role.User.powerLevel)
|
||||
)
|
||||
createRoomMemberModerationPresenter(room = room).test {
|
||||
val initialState = awaitState()
|
||||
|
|
@ -125,8 +125,8 @@ class RoomMemberModerationPresenterTest {
|
|||
val room = aJoinedRoom(
|
||||
canBan = true,
|
||||
canKick = true,
|
||||
myUserRole = RoomMember.Role.MODERATOR,
|
||||
targetRoomMember = aRoomMember(userId = A_USER_ID, powerLevel = RoomMember.Role.ADMIN.powerLevel)
|
||||
myUserRole = RoomMember.Role.Moderator,
|
||||
targetRoomMember = aRoomMember(userId = A_USER_ID, powerLevel = RoomMember.Role.Admin.powerLevel)
|
||||
)
|
||||
createRoomMemberModerationPresenter(room = room).test {
|
||||
val initialState = awaitState()
|
||||
|
|
@ -147,7 +147,7 @@ class RoomMemberModerationPresenterTest {
|
|||
val room = aJoinedRoom(
|
||||
canBan = true,
|
||||
canKick = true,
|
||||
myUserRole = RoomMember.Role.MODERATOR,
|
||||
myUserRole = RoomMember.Role.Moderator,
|
||||
targetRoomMember = aRoomMember(userId = A_USER_ID, membership = RoomMembershipState.BAN)
|
||||
)
|
||||
createRoomMemberModerationPresenter(room = room).test {
|
||||
|
|
@ -321,7 +321,7 @@ class RoomMemberModerationPresenterTest {
|
|||
private fun aJoinedRoom(
|
||||
canKick: Boolean = false,
|
||||
canBan: Boolean = false,
|
||||
myUserRole: RoomMember.Role = RoomMember.Role.USER,
|
||||
myUserRole: RoomMember.Role = RoomMember.Role.User,
|
||||
kickUserResult: Result<Unit> = Result.success(Unit),
|
||||
banUserResult: Result<Unit> = Result.success(Unit),
|
||||
unBanUserResult: Result<Unit> = Result.success(Unit),
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ class SecureBackupSetupPresenter @AssistedInject constructor(
|
|||
stateAndDispatch.dispatchAction(SecureBackupSetupStateMachine.Event.UserSavedKey)
|
||||
SecureBackupSetupEvents.DismissDialog -> {
|
||||
showSaveConfirmationDialog = false
|
||||
stateAndDispatch.dispatchAction(SecureBackupSetupStateMachine.Event.ClearError)
|
||||
}
|
||||
SecureBackupSetupEvents.Done -> {
|
||||
showSaveConfirmationDialog = true
|
||||
|
|
@ -89,6 +90,7 @@ class SecureBackupSetupPresenter @AssistedInject constructor(
|
|||
SecureBackupSetupStateMachine.State.CreatingKey -> SetupState.Creating
|
||||
is SecureBackupSetupStateMachine.State.KeyCreated -> SetupState.Created(formattedRecoveryKey = key)
|
||||
is SecureBackupSetupStateMachine.State.KeyCreatedAndSaved -> SetupState.CreatedAndSaved(formattedRecoveryKey = key)
|
||||
is SecureBackupSetupStateMachine.State.Error -> SetupState.Error(exception)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -103,13 +105,20 @@ class SecureBackupSetupPresenter @AssistedInject constructor(
|
|||
stateAndDispatch.dispatchAction(SecureBackupSetupStateMachine.Event.SdkHasCreatedKey(it))
|
||||
},
|
||||
onFailure = {
|
||||
stateAndDispatch.dispatchAction(SecureBackupSetupStateMachine.Event.SdkError(it))
|
||||
if (it is Exception) {
|
||||
stateAndDispatch.dispatchAction(SecureBackupSetupStateMachine.Event.SdkError(it))
|
||||
}
|
||||
}
|
||||
)
|
||||
} else {
|
||||
observeEncryptionService(stateAndDispatch)
|
||||
Timber.tag(loggerTagSetup.value).d("Calling encryptionService.enableRecovery()")
|
||||
encryptionService.enableRecovery(waitForBackupsToUpload = false)
|
||||
encryptionService.enableRecovery(waitForBackupsToUpload = false).onFailure {
|
||||
Timber.tag(loggerTagSetup.value).e(it, "Failed to enable recovery")
|
||||
if (it is Exception) {
|
||||
stateAndDispatch.dispatchAction(SecureBackupSetupStateMachine.Event.SdkError(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ sealed interface SetupState {
|
|||
data object Creating : SetupState
|
||||
data class Created(val formattedRecoveryKey: String) : SetupState
|
||||
data class CreatedAndSaved(val formattedRecoveryKey: String) : SetupState
|
||||
data class Error(val exception: Exception) : SetupState
|
||||
}
|
||||
|
||||
fun SetupState.recoveryKey(): String? = when (this) {
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ class SecureBackupSetupStateMachine @Inject constructor() : FlowReduxStateMachin
|
|||
}
|
||||
}
|
||||
inState<State.CreatingKey> {
|
||||
on { _: Event.SdkError, state: MachineState<State.CreatingKey> ->
|
||||
state.override { State.Initial }
|
||||
on { event: Event.SdkError, state: MachineState<State.CreatingKey> ->
|
||||
state.override { State.Error(event.exception) }
|
||||
}
|
||||
on { event: Event.SdkHasCreatedKey, state: MachineState<State.CreatingKey> ->
|
||||
state.override { State.KeyCreated(event.key) }
|
||||
|
|
@ -38,6 +38,11 @@ class SecureBackupSetupStateMachine @Inject constructor() : FlowReduxStateMachin
|
|||
state.override { State.KeyCreatedAndSaved(state.snapshot.key) }
|
||||
}
|
||||
}
|
||||
inState<State.Error> {
|
||||
on { _: Event.ClearError, state: MachineState<State.Error> ->
|
||||
state.override { State.Initial }
|
||||
}
|
||||
}
|
||||
inState<State.KeyCreatedAndSaved> {
|
||||
}
|
||||
}
|
||||
|
|
@ -48,12 +53,14 @@ class SecureBackupSetupStateMachine @Inject constructor() : FlowReduxStateMachin
|
|||
data object CreatingKey : State
|
||||
data class KeyCreated(val key: String) : State
|
||||
data class KeyCreatedAndSaved(val key: String) : State
|
||||
data class Error(val exception: Exception) : State
|
||||
}
|
||||
|
||||
sealed interface Event {
|
||||
data object UserCreatesKey : Event
|
||||
data class SdkHasCreatedKey(val key: String) : Event
|
||||
data class SdkError(val throwable: Throwable) : Event
|
||||
data class SdkError(val exception: Exception) : Event
|
||||
data object UserSavedKey : Event
|
||||
data object ClearError : Event
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ open class SecureBackupSetupStateProvider : PreviewParameterProvider<SecureBacku
|
|||
setupState = SetupState.CreatedAndSaved(aFormattedRecoveryKey()),
|
||||
showSaveConfirmationDialog = true,
|
||||
),
|
||||
aSecureBackupSetupState(setupState = SetupState.Error(Exception("Test error"))),
|
||||
// Add other states here
|
||||
)
|
||||
}
|
||||
|
|
|
|||
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