Merge branch 'release/25.08.0' into main
This commit is contained in:
commit
79b8113f00
636 changed files with 3380 additions and 2459 deletions
|
|
@ -4,6 +4,6 @@ appId: ${MAESTRO_APP_ID}
|
||||||
- tapOn:
|
- tapOn:
|
||||||
id: "text_editor"
|
id: "text_editor"
|
||||||
- inputText: "Hello world!"
|
- inputText: "Hello world!"
|
||||||
- tapOn: "Send"
|
- tapOn: "Send message"
|
||||||
- hideKeyboard
|
- hideKeyboard
|
||||||
- takeScreenshot: build/maestro/511-Timeline
|
- takeScreenshot: build/maestro/511-Timeline
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,9 @@ appId: ${MAESTRO_APP_ID}
|
||||||
- runFlow: messages/text.yaml
|
- runFlow: messages/text.yaml
|
||||||
- runFlow: messages/location.yaml
|
- runFlow: messages/location.yaml
|
||||||
- runFlow: messages/poll.yaml
|
- runFlow: messages/poll.yaml
|
||||||
- runFlow: call/call.yaml
|
|
||||||
|
# Restore once the call flow is fixed
|
||||||
|
#- runFlow: call/call.yaml
|
||||||
|
|
||||||
- back
|
- back
|
||||||
- runFlow: ../../assertions/assertHomeDisplayed.yaml
|
- 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
|
Changes in Element X v25.07.0
|
||||||
=============================
|
=============================
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ allprojects {
|
||||||
config.from(files("$rootDir/tools/detekt/detekt.yml"))
|
config.from(files("$rootDir/tools/detekt/detekt.yml"))
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
detektPlugins("io.nlopez.compose.rules:detekt:0.4.22")
|
detektPlugins("io.nlopez.compose.rules:detekt:0.4.26")
|
||||||
detektPlugins(project(":tests:detekt-rules"))
|
detektPlugins(project(":tests:detekt-rules"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@
|
||||||
|
|
||||||
This doc is a quick introduction about the project and its architecture.
|
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:
|
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
|
- 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".
|
`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 Android project
|
||||||
|
|
||||||
The project should compile out of the box.
|
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.
|
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
|
### 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
|
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>
|
<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.
|
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.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.analytics.api.AnalyticsOptInEvents
|
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.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.InfoListItem
|
||||||
import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrganism
|
import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrganism
|
||||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||||
import io.element.android.libraries.designsystem.background.OnboardingBackground
|
import io.element.android.libraries.designsystem.background.OnboardingBackground
|
||||||
import io.element.android.libraries.designsystem.components.BigIcon
|
import io.element.android.libraries.designsystem.components.BigIcon
|
||||||
import io.element.android.libraries.designsystem.components.ClickableLinkText
|
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.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.text.buildAnnotatedStringWithStyledPart
|
import io.element.android.libraries.designsystem.text.buildAnnotatedStringWithStyledPart
|
||||||
|
|
@ -89,10 +89,10 @@ private fun AnalyticsOptInHeader(
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
PageTitle(
|
IconTitleSubtitleMolecule(
|
||||||
modifier = Modifier.padding(top = 60.dp, bottom = 12.dp),
|
modifier = Modifier.padding(top = 60.dp, bottom = 28.dp),
|
||||||
title = stringResource(id = R.string.screen_analytics_prompt_title, state.applicationName),
|
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())
|
iconStyle = BigIcon.Style.Default(CompoundIcons.Chart())
|
||||||
)
|
)
|
||||||
if (state.hasPolicyLink) {
|
if (state.hasPolicyLink) {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
import kotlinx.serialization.Transient
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
@ -84,6 +86,11 @@ class WebViewAudioManager(
|
||||||
?.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, "${webView.context.packageName}:ProximitySensorCallWakeLock")
|
?.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.
|
* This listener tracks the current communication device and updates the WebView when it changes.
|
||||||
*/
|
*/
|
||||||
|
|
@ -208,8 +215,12 @@ class WebViewAudioManager(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (proximitySensorWakeLock?.isHeld == true) {
|
coroutineScope.launch {
|
||||||
proximitySensorWakeLock?.release()
|
proximitySensorMutex.withLock {
|
||||||
|
if (proximitySensorWakeLock?.isHeld == true) {
|
||||||
|
proximitySensorWakeLock?.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
audioManager.mode = AudioManager.MODE_NORMAL
|
audioManager.mode = AudioManager.MODE_NORMAL
|
||||||
|
|
@ -397,13 +408,17 @@ class WebViewAudioManager(
|
||||||
|
|
||||||
expectedNewCommunicationDeviceId = null
|
expectedNewCommunicationDeviceId = null
|
||||||
|
|
||||||
@Suppress("WakeLock", "WakeLockTimeout")
|
coroutineScope.launch {
|
||||||
if (device?.type == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE && proximitySensorWakeLock?.isHeld == false) {
|
proximitySensorMutex.withLock {
|
||||||
// If the device is the built-in earpiece, we need to acquire the proximity sensor wake lock
|
@Suppress("WakeLock", "WakeLockTimeout")
|
||||||
proximitySensorWakeLock?.acquire()
|
if (device?.type == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE && proximitySensorWakeLock?.isHeld == false) {
|
||||||
} else if (proximitySensorWakeLock?.isHeld == true) {
|
// If the device is the built-in earpiece, we need to acquire the proximity sensor wake lock
|
||||||
// If the device is no longer the earpiece, we need to release the wake lock
|
proximitySensorWakeLock?.acquire()
|
||||||
proximitySensorWakeLock?.release()
|
} 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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="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_message_android">"Kõne juurde naasmiseks klõpsa"</string>
|
||||||
<string name="call_foreground_service_title_android">"☎️ Kõne on pooleli"</string>
|
<string name="call_foreground_service_title_android">"☎️ Kõne on pooleli"</string>
|
||||||
<string name="call_invalid_audio_device_bluetooth_devices_disabled">"Element Call ei võimalda selles Androidi versioonis Bluetoothi heliseadmete kasutamist. Palun vali mõni muu heliseade."</string>
|
<string name="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_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_message_android">"Stuknij, aby wrócić do rozmowy"</string>
|
||||||
<string name="call_foreground_service_title_android">"☎️ Rozmowa w toku"</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>
|
<string name="screen_incoming_call_subtitle_android">"Przychodzące połączenie Element"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,8 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.res.stringResource
|
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.text.input.KeyboardCapitalization
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
@ -213,11 +215,19 @@ private fun RoomNameWithAvatar(
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
|
val a11yAvatar = stringResource(CommonStrings.a11y_room_avatar)
|
||||||
UnsavedAvatar(
|
UnsavedAvatar(
|
||||||
avatarUri = avatarUri,
|
avatarUri = avatarUri,
|
||||||
avatarSize = AvatarSize.EditRoomDetails,
|
avatarSize = AvatarSize.EditRoomDetails,
|
||||||
avatarType = AvatarType.Room(),
|
avatarType = AvatarType.Room(),
|
||||||
modifier = Modifier.clickable(onClick = onAvatarClick),
|
modifier = Modifier
|
||||||
|
.clickable(
|
||||||
|
onClick = onAvatarClick,
|
||||||
|
onClickLabel = stringResource(CommonStrings.action_open_context_menu),
|
||||||
|
)
|
||||||
|
.clearAndSetSemantics {
|
||||||
|
contentDescription = a11yAvatar
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
TextField(
|
TextField(
|
||||||
|
|
|
||||||
|
|
@ -31,10 +31,10 @@ import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.ftue.impl.R
|
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.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.atomic.pages.HeaderFooterPage
|
||||||
import io.element.android.libraries.designsystem.background.OnboardingBackground
|
import io.element.android.libraries.designsystem.background.OnboardingBackground
|
||||||
import io.element.android.libraries.designsystem.components.BigIcon
|
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.Avatar
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||||
|
|
@ -59,7 +59,7 @@ fun NotificationsOptInView(
|
||||||
.statusBarsPadding()
|
.statusBarsPadding()
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
background = { OnboardingBackground() },
|
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) },
|
footer = { NotificationsOptInFooter(state) },
|
||||||
) {
|
) {
|
||||||
NotificationsOptInContent()
|
NotificationsOptInContent()
|
||||||
|
|
@ -70,10 +70,10 @@ fun NotificationsOptInView(
|
||||||
private fun NotificationsOptInHeader(
|
private fun NotificationsOptInHeader(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
PageTitle(
|
IconTitleSubtitleMolecule(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
title = stringResource(R.string.screen_notification_optin_title),
|
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()),
|
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.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.ftue.impl.R
|
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.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.atomic.pages.HeaderFooterPage
|
||||||
import io.element.android.libraries.designsystem.components.BigIcon
|
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.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Button
|
import io.element.android.libraries.designsystem.theme.components.Button
|
||||||
|
|
@ -65,10 +65,11 @@ fun ChooseSelfVerificationModeView(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
header = {
|
header = {
|
||||||
PageTitle(
|
IconTitleSubtitleMolecule(
|
||||||
|
modifier = Modifier.padding(bottom = 16.dp),
|
||||||
iconStyle = BigIcon.Style.Default(CompoundIcons.LockSolid()),
|
iconStyle = BigIcon.Style.Default(CompoundIcons.LockSolid()),
|
||||||
title = stringResource(id = R.string.screen_identity_confirmation_title),
|
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 = {
|
footer = {
|
||||||
|
|
|
||||||
|
|
@ -274,13 +274,11 @@ private fun HomeScaffold(
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
if (state.displayActions) {
|
if (state.displayActions) {
|
||||||
FloatingActionButton(
|
FloatingActionButton(
|
||||||
containerColor = ElementTheme.colors.iconPrimary,
|
onClick = onCreateRoomClick,
|
||||||
onClick = onCreateRoomClick
|
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = CompoundIcons.Plus(),
|
imageVector = CompoundIcons.Plus(),
|
||||||
contentDescription = stringResource(id = R.string.screen_roomlist_a11y_create_message),
|
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="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_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="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_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_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>
|
<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="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_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="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_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_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>
|
<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="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_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="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_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_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>
|
<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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="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_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_submit">"Ota palautus käyttöön"</string>
|
||||||
<string name="banner_set_up_recovery_title">"Ota palautus käyttöön tilisi suojaamiseksi"</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="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_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="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_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_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>
|
<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="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_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="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_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_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>
|
<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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="screen_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_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_chat_title">"Atmesti kvietimą"</string>
|
||||||
<string name="screen_invites_decline_direct_chat_message">"Ar tikrai norite atmesti šį privatų pokalbį su %1$s ?"</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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="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_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_submit">"Skonfiguruj przywracanie"</string>
|
||||||
<string name="banner_set_up_recovery_title">"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="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_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="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_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_chat_title">"Odrzuć zaproszenie"</string>
|
||||||
<string name="screen_invites_decline_direct_chat_message">"Czy na pewno chcesz odrzucić rozmowę prywatną z %1$s?"</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_message">"Jest to jednorazowy proces, dziękujemy za czekanie."</string>
|
||||||
<string name="screen_migration_title">"Konfigurowanie Twojego konta."</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_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_message">"Wyślij komuś wiadomość, aby rozpocząć."</string>
|
||||||
<string name="screen_roomlist_empty_title">"Brak czatów."</string>
|
<string name="screen_roomlist_empty_title">"Brak czatów."</string>
|
||||||
<string name="screen_roomlist_filter_favourites">"Ulubione"</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_main_space_title">"Wszystkie czaty"</string>
|
||||||
<string name="screen_roomlist_mark_as_read">"Oznacz jako przeczytane"</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_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_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>
|
<string name="session_verification_banner_title">"Potwierdź, że to Ty"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@
|
||||||
<string name="confirm_recovery_key_banner_title">"Vaše úložisko kľúčov nie je synchronizované"</string>
|
<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_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="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_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_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>
|
<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_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_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_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_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_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>
|
<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_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_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_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_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_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>
|
<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_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_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_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_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_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>
|
<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_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_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_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_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_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>
|
<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.core.meta.BuildMeta
|
||||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
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.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.libraries.textcomposer.model.MessageComposerMode
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -103,17 +100,7 @@ class SendLocationPresenter @Inject constructor(
|
||||||
mode: SendLocationState.Mode,
|
mode: SendLocationState.Mode,
|
||||||
) {
|
) {
|
||||||
val replyMode = messageComposerContext.composerMode as? MessageComposerMode.Reply
|
val replyMode = messageComposerContext.composerMode as? MessageComposerMode.Reply
|
||||||
val replyParams = replyMode?.replyToDetails?.let { details ->
|
val inReplyToEventId = replyMode?.eventId
|
||||||
if (replyMode.inThread) {
|
|
||||||
replyInThread(details.eventId())
|
|
||||||
} else {
|
|
||||||
ReplyParameters(
|
|
||||||
inReplyToEventId = details.eventId(),
|
|
||||||
enforceThreadReply = false,
|
|
||||||
replyWithinThread = false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
when (mode) {
|
when (mode) {
|
||||||
SendLocationState.Mode.PinLocation -> {
|
SendLocationState.Mode.PinLocation -> {
|
||||||
val geoUri = event.cameraPosition.toGeoUri()
|
val geoUri = event.cameraPosition.toGeoUri()
|
||||||
|
|
@ -123,7 +110,7 @@ class SendLocationPresenter @Inject constructor(
|
||||||
description = null,
|
description = null,
|
||||||
zoomLevel = MapDefaults.DEFAULT_ZOOM.toInt(),
|
zoomLevel = MapDefaults.DEFAULT_ZOOM.toInt(),
|
||||||
assetType = AssetType.PIN,
|
assetType = AssetType.PIN,
|
||||||
replyParameters = replyParams,
|
inReplyToEventId = inReplyToEventId,
|
||||||
)
|
)
|
||||||
analyticsService.capture(
|
analyticsService.capture(
|
||||||
Composer(
|
Composer(
|
||||||
|
|
@ -142,7 +129,7 @@ class SendLocationPresenter @Inject constructor(
|
||||||
description = null,
|
description = null,
|
||||||
zoomLevel = MapDefaults.DEFAULT_ZOOM.toInt(),
|
zoomLevel = MapDefaults.DEFAULT_ZOOM.toInt(),
|
||||||
assetType = AssetType.SENDER,
|
assetType = AssetType.SENDER,
|
||||||
replyParameters = replyParams,
|
inReplyToEventId = inReplyToEventId,
|
||||||
)
|
)
|
||||||
analyticsService.capture(
|
analyticsService.capture(
|
||||||
Composer(
|
Composer(
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.unit.dp
|
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.Location
|
||||||
import io.element.android.features.location.api.internal.centerBottomEdge
|
import io.element.android.features.location.api.internal.centerBottomEdge
|
||||||
import io.element.android.features.location.api.internal.rememberTileStyleUrl
|
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.MapDefaults
|
||||||
import io.element.android.features.location.impl.common.PermissionDeniedDialog
|
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.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.components.button.BackButton
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.BottomSheetScaffold
|
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.Icon
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
|
|
@ -189,17 +188,13 @@ fun SendLocationView(
|
||||||
tint = Color.Unspecified,
|
tint = Color.Unspecified,
|
||||||
modifier = Modifier.centerBottomEdge(this),
|
modifier = Modifier.centerBottomEdge(this),
|
||||||
)
|
)
|
||||||
FloatingActionButton(
|
LocationFloatingActionButton(
|
||||||
|
isMapCenteredOnUser = state.mode == SendLocationState.Mode.SenderLocation,
|
||||||
onClick = { state.eventSink(SendLocationEvents.SwitchToMyLocationMode) },
|
onClick = { state.eventSink(SendLocationEvents.SwitchToMyLocationMode) },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.BottomEnd)
|
.align(Alignment.BottomEnd)
|
||||||
.padding(end = 16.dp, bottom = 72.dp + navBarPadding),
|
.padding(end = 18.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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.MapDefaults
|
||||||
import io.element.android.features.location.impl.common.PermissionDeniedDialog
|
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.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.components.button.BackButton
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.FloatingActionButton
|
|
||||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
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.IconButton
|
||||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||||
|
|
@ -118,14 +118,10 @@ fun ShowLocationView(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
floatingActionButton = {
|
floatingActionButton = {
|
||||||
FloatingActionButton(
|
LocationFloatingActionButton(
|
||||||
|
isMapCenteredOnUser = state.isTrackMyLocation,
|
||||||
onClick = { state.eventSink(ShowLocationEvents.TrackMyLocation(true)) },
|
onClick = { state.eventSink(ShowLocationEvents.TrackMyLocation(true)) },
|
||||||
) {
|
)
|
||||||
when (state.isTrackMyLocation) {
|
|
||||||
false -> Icon(imageVector = CompoundIcons.LocationNavigator(), contentDescription = null)
|
|
||||||
true -> Icon(imageVector = CompoundIcons.LocationNavigatorCentred(), contentDescription = null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Column(
|
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.PermissionsPresenter
|
||||||
import io.element.android.features.location.impl.common.permissions.PermissionsState
|
import io.element.android.features.location.impl.common.permissions.PermissionsState
|
||||||
import io.element.android.features.messages.test.FakeMessageComposerContext
|
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.JoinedRoom
|
||||||
import io.element.android.libraries.matrix.api.room.location.AssetType
|
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.api.timeline.item.event.toEventOrTransactionId
|
||||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||||
|
|
@ -264,7 +264,7 @@ class SendLocationPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `share sender location`() = runTest {
|
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)
|
Result.success(Unit)
|
||||||
}
|
}
|
||||||
val joinedRoom = FakeJoinedRoom(
|
val joinedRoom = FakeJoinedRoom(
|
||||||
|
|
@ -328,7 +328,7 @@ class SendLocationPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `share pin location`() = runTest {
|
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)
|
Result.success(Unit)
|
||||||
}
|
}
|
||||||
val joinedRoom = FakeJoinedRoom(
|
val joinedRoom = FakeJoinedRoom(
|
||||||
|
|
@ -392,7 +392,7 @@ class SendLocationPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `composer context passes through analytics`() = runTest {
|
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)
|
Result.success(Unit)
|
||||||
}
|
}
|
||||||
val joinedRoom = FakeJoinedRoom(
|
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
|
* This class is responsible for managing the login flow, including handling OIDC actions and
|
||||||
* submitting login requests.
|
* 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].
|
* and [ChooseAccountProviderPresenter].
|
||||||
*/
|
*/
|
||||||
class LoginHelper @Inject constructor(
|
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_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_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_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_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_change_server">"Skift kontoudbyder"</string>
|
||||||
<string name="screen_server_confirmation_message_login_element_dot_io">"En privat server til Element-medarbejdere."</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_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_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_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_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:
|
<string name="screen_change_server_error_invalid_well_known">"Server pole saadaval vea tõttu well-known failis:
|
||||||
%1$s"</string>
|
%1$s"</string>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@
|
||||||
<string name="screen_change_account_provider_other">"Autres"</string>
|
<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_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_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_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:
|
<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>
|
%1$s"</string>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@
|
||||||
<string name="screen_change_account_provider_other">"Egyéb"</string>
|
<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_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_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_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:
|
<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>
|
%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_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">"Örülünk, hogy visszatért!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Bejelentkezés ide: %1$s"</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_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_to">"Bejelentkezés ide: %1$s"</string>
|
||||||
<string name="screen_onboarding_sign_in_with_qr_code">"Bejelentkezés QR-kóddal"</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_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_form_header">"Serverio URL"</string>
|
||||||
<string name="screen_change_server_subtitle">"Koks yra Jūsų serverio adresas?"</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_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_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>
|
<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_subtitle">"Matrix yra atviras tinklas, skirtas saugiam, decentralizuotam bendravimui."</string>
|
||||||
<string name="screen_login_title">"Sveiki sugrįžę!"</string>
|
<string name="screen_login_title">"Sveiki sugrįžę!"</string>
|
||||||
<string name="screen_login_title_with_homeserver">"Prisijungti prie %1$s"</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_app_version">"%1$s versija"</string>
|
||||||
<string name="screen_onboarding_sign_in_with_qr_code">"Prisijunkite naudodami QR kodą"</string>
|
<string name="screen_onboarding_sign_in_manually">"Prisijungti rankiniu būdu"</string>
|
||||||
<string name="screen_onboarding_sign_up">"Sukurti paskyrą"</string>
|
<string name="screen_onboarding_sign_in_to">"Prisijungti prie %1$s"</string>
|
||||||
<string name="screen_onboarding_welcome_subtitle">"Sveiki atvykę į %1$s. Įkrautas greitumui ir paprastumui."</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_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_change_server">"Keisti paskyros teikėją"</string>
|
||||||
<string name="screen_server_confirmation_message_login_element_dot_io">"Privatus serveris “Element” darbuotojams."</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_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_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_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_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:
|
<string name="screen_change_server_error_invalid_well_known">"Serwer nie jest dostępny z powodu problemu pliku .well-known:
|
||||||
%1$s"</string>
|
%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_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">"%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_header">"URL serwera domowego"</string>
|
||||||
<string name="screen_change_server_form_notice">"Wprowadź adres domeny."</string>
|
<string name="screen_change_server_form_notice">"Wprowadź adres domeny."</string>
|
||||||
<string name="screen_change_server_subtitle">"Jaki jest adres Twojego serwera?"</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_subtitle">"Matrix to otwarta sieć do bezpiecznej i zdecentralizowanej komunikacji."</string>
|
||||||
<string name="screen_login_title">"Witaj ponownie!"</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_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_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_to">"Zaloguj się do %1$s"</string>
|
||||||
<string name="screen_onboarding_sign_in_with_qr_code">"Zaloguj się za pomocą kodu QR"</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_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_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_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>
|
<string name="screen_server_confirmation_title_register">"Zamierzasz utworzyć konto na %1$s"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@
|
||||||
<string name="screen_change_account_provider_other">"Iný"</string>
|
<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_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_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_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:
|
<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>
|
%1$s"</string>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@
|
||||||
<string name="screen_change_account_provider_other">"Other"</string>
|
<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_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_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_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:
|
<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$s"</string>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_signout_confirmation_dialog_content">"Ar tikrai norite atsijungti?"</string>
|
<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_in_progress_dialog_content">"Atsijungiama…"</string>
|
||||||
|
<string name="screen_signout_preference_item">"Atsijungti"</string>
|
||||||
</resources>
|
</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.di.annotations.SessionCoroutineScope
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
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.core.ProgressCallback
|
||||||
import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
|
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.MediaSender
|
||||||
import io.element.android.libraries.mediaupload.api.MediaUploadInfo
|
import io.element.android.libraries.mediaupload.api.MediaUploadInfo
|
||||||
import io.element.android.libraries.mediaupload.api.allFiles
|
import io.element.android.libraries.mediaupload.api.allFiles
|
||||||
|
|
@ -129,14 +129,19 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
|
||||||
caption = caption,
|
caption = caption,
|
||||||
sendActionState = sendActionState,
|
sendActionState = sendActionState,
|
||||||
dismissAfterSend = !useSendQueue,
|
dismissAfterSend = !useSendQueue,
|
||||||
replyParameters = null,
|
inReplyToEventId = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Clean up the pre-processed media after it's been sent
|
||||||
|
mediaSender.cleanUp()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AttachmentsPreviewEvents.CancelAndDismiss -> {
|
AttachmentsPreviewEvents.CancelAndDismiss -> {
|
||||||
// Cancel media preprocessing and sending
|
// Cancel media preprocessing and sending
|
||||||
preprocessMediaJob?.cancel()
|
preprocessMediaJob?.cancel()
|
||||||
|
// If we couldn't send the pre-processed media, remove it
|
||||||
|
mediaSender.cleanUp()
|
||||||
ongoingSendAttachmentJob.value?.cancel()
|
ongoingSendAttachmentJob.value?.cancel()
|
||||||
|
|
||||||
// Dismiss the screen
|
// Dismiss the screen
|
||||||
|
|
@ -240,7 +245,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
|
||||||
caption: String?,
|
caption: String?,
|
||||||
sendActionState: MutableState<SendActionState>,
|
sendActionState: MutableState<SendActionState>,
|
||||||
dismissAfterSend: Boolean,
|
dismissAfterSend: Boolean,
|
||||||
replyParameters: ReplyParameters?,
|
inReplyToEventId: EventId?,
|
||||||
) = runCatchingExceptions {
|
) = runCatchingExceptions {
|
||||||
val context = coroutineContext
|
val context = coroutineContext
|
||||||
val progressCallback = object : ProgressCallback {
|
val progressCallback = object : ProgressCallback {
|
||||||
|
|
@ -256,7 +261,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
|
||||||
caption = caption,
|
caption = caption,
|
||||||
formattedCaption = null,
|
formattedCaption = null,
|
||||||
progressCallback = progressCallback,
|
progressCallback = progressCallback,
|
||||||
replyParameters = replyParameters,
|
inReplyToEventId = inReplyToEventId,
|
||||||
).getOrThrow()
|
).getOrThrow()
|
||||||
}.fold(
|
}.fold(
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ fun AttachmentsPreviewView(
|
||||||
state.eventSink(AttachmentsPreviewEvents.CancelAndClearSendState)
|
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()
|
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.ComposerDraft
|
||||||
import io.element.android.libraries.matrix.api.room.draft.ComposerDraftType
|
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.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.TimelineException
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId
|
import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId
|
||||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
|
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
|
||||||
|
|
@ -466,12 +465,7 @@ class MessageComposerPresenter @AssistedInject constructor(
|
||||||
body = message.markdown,
|
body = message.markdown,
|
||||||
htmlBody = message.html,
|
htmlBody = message.html,
|
||||||
intentionalMentions = message.intentionalMentions,
|
intentionalMentions = message.intentionalMentions,
|
||||||
replyParameters = ReplyParameters(
|
repliedToEventId = eventId,
|
||||||
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,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,7 @@ internal fun SuggestionsPickerViewPreview() {
|
||||||
powerLevel = 0L,
|
powerLevel = 0L,
|
||||||
normalizedPowerLevel = 0L,
|
normalizedPowerLevel = 0L,
|
||||||
isIgnored = false,
|
isIgnored = false,
|
||||||
role = RoomMember.Role.USER,
|
role = RoomMember.Role.User,
|
||||||
membershipChangeReason = null,
|
membershipChangeReason = null,
|
||||||
)
|
)
|
||||||
val anAlias = remember { RoomAlias("#room:domain.org") }
|
val anAlias = remember { RoomAlias("#room:domain.org") }
|
||||||
|
|
|
||||||
|
|
@ -366,7 +366,7 @@ private fun JumpToBottomButton(
|
||||||
shape = CircleShape,
|
shape = CircleShape,
|
||||||
modifier = Modifier.size(36.dp),
|
modifier = Modifier.size(36.dp),
|
||||||
containerColor = ElementTheme.colors.bgSubtleSecondary,
|
containerColor = ElementTheme.colors.bgSubtleSecondary,
|
||||||
contentColor = ElementTheme.colors.iconSecondary
|
contentColor = ElementTheme.colors.iconSecondary,
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ package io.element.android.features.messages.impl.timeline.components
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.BoxScope
|
||||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
|
@ -20,7 +21,6 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.draw.drawWithContent
|
import androidx.compose.ui.draw.drawWithContent
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.graphics.BlendMode
|
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.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.text.toDp
|
import io.element.android.libraries.designsystem.text.toDp
|
||||||
import io.element.android.libraries.designsystem.text.toPx
|
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.components.Text
|
||||||
import io.element.android.libraries.designsystem.theme.messageFromMeBackground
|
import io.element.android.libraries.designsystem.theme.messageFromMeBackground
|
||||||
import io.element.android.libraries.designsystem.theme.messageFromOtherBackground
|
import io.element.android.libraries.designsystem.theme.messageFromOtherBackground
|
||||||
|
|
@ -64,7 +63,7 @@ fun MessageEventBubble(
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
content: @Composable () -> Unit = {},
|
content: @Composable BoxScope.() -> Unit = {},
|
||||||
) {
|
) {
|
||||||
fun bubbleShape(): Shape {
|
fun bubbleShape(): Shape {
|
||||||
val topLeftCorner = if (state.cutTopStart) 0.dp else BUBBLE_RADIUS
|
val topLeftCorner = if (state.cutTopStart) 0.dp else BUBBLE_RADIUS
|
||||||
|
|
@ -117,9 +116,12 @@ fun MessageEventBubble(
|
||||||
BoxWithConstraints(
|
BoxWithConstraints(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.graphicsLayer {
|
.graphicsLayer {
|
||||||
|
shape = bubbleShape
|
||||||
|
clip = true
|
||||||
compositingStrategy = CompositingStrategy.Offscreen
|
compositingStrategy = CompositingStrategy.Offscreen
|
||||||
}
|
}
|
||||||
.drawWithContent {
|
.drawWithContent {
|
||||||
|
drawRect(backgroundBubbleColor)
|
||||||
drawContent()
|
drawContent()
|
||||||
if (state.cutTopStart) {
|
if (state.cutTopStart) {
|
||||||
drawCircle(
|
drawCircle(
|
||||||
|
|
@ -137,7 +139,7 @@ fun MessageEventBubble(
|
||||||
// when content width is low.
|
// when content width is low.
|
||||||
contentAlignment = if (state.isMine) Alignment.CenterEnd else Alignment.CenterStart
|
contentAlignment = if (state.isMine) Alignment.CenterEnd else Alignment.CenterStart
|
||||||
) {
|
) {
|
||||||
Surface(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.testTag(TestTags.messageBubble)
|
.testTag(TestTags.messageBubble)
|
||||||
.widthIn(
|
.widthIn(
|
||||||
|
|
@ -146,11 +148,8 @@ fun MessageEventBubble(
|
||||||
.toInt()
|
.toInt()
|
||||||
.toDp()
|
.toDp()
|
||||||
)
|
)
|
||||||
.clip(bubbleShape)
|
|
||||||
.then(clickableModifier),
|
.then(clickableModifier),
|
||||||
color = backgroundBubbleColor,
|
content = content,
|
||||||
shape = bubbleShape,
|
|
||||||
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.text.toAnnotatedString
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.designsystem.utils.allBooleans
|
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.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.room.tombstone.PredecessorRoom
|
import io.element.android.libraries.matrix.api.room.tombstone.PredecessorRoom
|
||||||
|
|
||||||
|
|
@ -92,7 +91,7 @@ internal fun TimelineItemRoomBeginningViewPreview() = ElementPreview {
|
||||||
onPredecessorRoomClick = {},
|
onPredecessorRoomClick = {},
|
||||||
)
|
)
|
||||||
TimelineItemRoomBeginningView(
|
TimelineItemRoomBeginningView(
|
||||||
predecessorRoom = PredecessorRoom(RoomId("!roomId:matrix.org"), EventId("\$eventId:matrix.org")),
|
predecessorRoom = PredecessorRoom(RoomId("!roomId:matrix.org")),
|
||||||
roomName = "Room Name",
|
roomName = "Room Name",
|
||||||
isDm = isDm,
|
isDm = isDm,
|
||||||
onPredecessorRoomClick = {},
|
onPredecessorRoomClick = {},
|
||||||
|
|
|
||||||
|
|
@ -35,14 +35,30 @@
|
||||||
<string name="screen_room_timeline_less_reactions">"Pokaż mniej"</string>
|
<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_message_copied">"Skopiowano wiadomość"</string>
|
||||||
<string name="screen_room_timeline_no_permission_to_post">"Nie masz uprawnień, aby pisać w tym pokoju"</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_less">"Pokaż mniej"</string>
|
||||||
<string name="screen_room_timeline_reactions_show_more">"Pokaż więcej"</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>
|
<string name="screen_room_timeline_read_marker_title">"Nowe"</string>
|
||||||
<plurals name="screen_room_timeline_state_changes">
|
<plurals name="screen_room_timeline_state_changes">
|
||||||
<item quantity="one">"%1$d zmiana pokoju"</item>
|
<item quantity="one">"%1$d zmiana pokoju"</item>
|
||||||
<item quantity="few">"%1$d zmiany pokoju"</item>
|
<item quantity="few">"%1$d zmiany pokoju"</item>
|
||||||
<item quantity="many">"%1$d zmian pokoju"</item>
|
<item quantity="many">"%1$d zmian pokoju"</item>
|
||||||
</plurals>
|
</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">
|
<plurals name="screen_room_typing_many_members">
|
||||||
<item quantity="one">"%1$s, %2$s i %3$d inny"</item>
|
<item quantity="one">"%1$s, %2$s i %3$d inny"</item>
|
||||||
<item quantity="few">"%1$s, %2$s i %3$d innych"</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.core.mimetype.MimeTypes
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
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.core.ProgressCallback
|
||||||
import io.element.android.libraries.matrix.api.media.AudioInfo
|
import io.element.android.libraries.matrix.api.media.AudioInfo
|
||||||
import io.element.android.libraries.matrix.api.media.FileInfo
|
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.media.VideoInfo
|
||||||
import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
|
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.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.A_CAPTION
|
||||||
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
|
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
|
||||||
import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder
|
import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder
|
||||||
|
|
@ -108,7 +108,7 @@ class AttachmentsPreviewPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - send media success scenario`() = runTest {
|
fun `present - send media success scenario`() = runTest {
|
||||||
val sendFileResult =
|
val sendFileResult =
|
||||||
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
|
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, EventId?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
|
||||||
Result.success(FakeMediaUploadHandler())
|
Result.success(FakeMediaUploadHandler())
|
||||||
}
|
}
|
||||||
val room = FakeJoinedRoom(
|
val room = FakeJoinedRoom(
|
||||||
|
|
@ -123,8 +123,10 @@ class AttachmentsPreviewPresenterTest {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
val onDoneListener = lambdaRecorder<Unit> { }
|
val onDoneListener = lambdaRecorder<Unit> { }
|
||||||
|
val mediaPreProcessor = FakeMediaPreProcessor()
|
||||||
val presenter = createAttachmentsPreviewPresenter(
|
val presenter = createAttachmentsPreviewPresenter(
|
||||||
room = room,
|
room = room,
|
||||||
|
mediaPreProcessor = mediaPreProcessor,
|
||||||
onDoneListener = { onDoneListener() },
|
onDoneListener = { onDoneListener() },
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
|
@ -143,13 +145,14 @@ class AttachmentsPreviewPresenterTest {
|
||||||
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
|
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
|
||||||
sendFileResult.assertions().isCalledOnce()
|
sendFileResult.assertions().isCalledOnce()
|
||||||
onDoneListener.assertions().isCalledOnce()
|
onDoneListener.assertions().isCalledOnce()
|
||||||
|
assertThat(mediaPreProcessor.cleanUpCallCount).isEqualTo(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - send media after pre-processing success scenario`() = runTest {
|
fun `present - send media after pre-processing success scenario`() = runTest {
|
||||||
val sendFileResult =
|
val sendFileResult =
|
||||||
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
|
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, EventId?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
|
||||||
Result.success(FakeMediaUploadHandler())
|
Result.success(FakeMediaUploadHandler())
|
||||||
}
|
}
|
||||||
val room = FakeJoinedRoom(
|
val room = FakeJoinedRoom(
|
||||||
|
|
@ -159,11 +162,10 @@ class AttachmentsPreviewPresenterTest {
|
||||||
)
|
)
|
||||||
val onDoneListener = lambdaRecorder<Unit> { }
|
val onDoneListener = lambdaRecorder<Unit> { }
|
||||||
val processLatch = CompletableDeferred<Unit>()
|
val processLatch = CompletableDeferred<Unit>()
|
||||||
|
val mediaPreProcessor = FakeMediaPreProcessor(processLatch)
|
||||||
val presenter = createAttachmentsPreviewPresenter(
|
val presenter = createAttachmentsPreviewPresenter(
|
||||||
room = room,
|
room = room,
|
||||||
mediaPreProcessor = FakeMediaPreProcessor(
|
mediaPreProcessor = mediaPreProcessor,
|
||||||
processLatch = processLatch,
|
|
||||||
),
|
|
||||||
onDoneListener = { onDoneListener() },
|
onDoneListener = { onDoneListener() },
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
|
@ -181,13 +183,14 @@ class AttachmentsPreviewPresenterTest {
|
||||||
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
|
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
|
||||||
sendFileResult.assertions().isCalledOnce()
|
sendFileResult.assertions().isCalledOnce()
|
||||||
onDoneListener.assertions().isCalledOnce()
|
onDoneListener.assertions().isCalledOnce()
|
||||||
|
assertThat(mediaPreProcessor.cleanUpCallCount).isEqualTo(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - send media before pre-processing success scenario`() = runTest {
|
fun `present - send media before pre-processing success scenario`() = runTest {
|
||||||
val sendFileResult =
|
val sendFileResult =
|
||||||
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
|
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, EventId?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
|
||||||
Result.success(FakeMediaUploadHandler())
|
Result.success(FakeMediaUploadHandler())
|
||||||
}
|
}
|
||||||
val room = FakeJoinedRoom(
|
val room = FakeJoinedRoom(
|
||||||
|
|
@ -197,11 +200,10 @@ class AttachmentsPreviewPresenterTest {
|
||||||
)
|
)
|
||||||
val onDoneListener = lambdaRecorder<Unit> { }
|
val onDoneListener = lambdaRecorder<Unit> { }
|
||||||
val processLatch = CompletableDeferred<Unit>()
|
val processLatch = CompletableDeferred<Unit>()
|
||||||
|
val mediaPreProcessor = FakeMediaPreProcessor(processLatch)
|
||||||
val presenter = createAttachmentsPreviewPresenter(
|
val presenter = createAttachmentsPreviewPresenter(
|
||||||
room = room,
|
room = room,
|
||||||
mediaPreProcessor = FakeMediaPreProcessor(
|
mediaPreProcessor = mediaPreProcessor,
|
||||||
processLatch = processLatch,
|
|
||||||
),
|
|
||||||
onDoneListener = { onDoneListener() },
|
onDoneListener = { onDoneListener() },
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
|
@ -219,6 +221,7 @@ class AttachmentsPreviewPresenterTest {
|
||||||
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
|
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
|
||||||
sendFileResult.assertions().isCalledOnce()
|
sendFileResult.assertions().isCalledOnce()
|
||||||
onDoneListener.assertions().isCalledOnce()
|
onDoneListener.assertions().isCalledOnce()
|
||||||
|
assertThat(mediaPreProcessor.cleanUpCallCount).isEqualTo(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -279,7 +282,9 @@ class AttachmentsPreviewPresenterTest {
|
||||||
fun `present - cancel scenario`() = runTest {
|
fun `present - cancel scenario`() = runTest {
|
||||||
val onDoneListener = lambdaRecorder<Unit> { }
|
val onDoneListener = lambdaRecorder<Unit> { }
|
||||||
val deleteCallback = lambdaRecorder<Uri?, Unit> {}
|
val deleteCallback = lambdaRecorder<Uri?, Unit> {}
|
||||||
|
val mediaPreProcessor = FakeMediaPreProcessor()
|
||||||
val presenter = createAttachmentsPreviewPresenter(
|
val presenter = createAttachmentsPreviewPresenter(
|
||||||
|
mediaPreProcessor = mediaPreProcessor,
|
||||||
temporaryUriDeleter = FakeTemporaryUriDeleter(deleteCallback),
|
temporaryUriDeleter = FakeTemporaryUriDeleter(deleteCallback),
|
||||||
onDoneListener = { onDoneListener() },
|
onDoneListener = { onDoneListener() },
|
||||||
)
|
)
|
||||||
|
|
@ -293,13 +298,14 @@ class AttachmentsPreviewPresenterTest {
|
||||||
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
|
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
|
||||||
deleteCallback.assertions().isCalledOnce()
|
deleteCallback.assertions().isCalledOnce()
|
||||||
onDoneListener.assertions().isCalledOnce()
|
onDoneListener.assertions().isCalledOnce()
|
||||||
|
assertThat(mediaPreProcessor.cleanUpCallCount).isEqualTo(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - send image with caption success scenario`() = runTest {
|
fun `present - send image with caption success scenario`() = runTest {
|
||||||
val sendImageResult =
|
val sendImageResult =
|
||||||
lambdaRecorder { _: File, _: File?, _: ImageInfo, _: String?, _: String?, _: ProgressCallback?, _: ReplyParameters? ->
|
lambdaRecorder { _: File, _: File?, _: ImageInfo, _: String?, _: String?, _: ProgressCallback?, _: EventId? ->
|
||||||
Result.success(FakeMediaUploadHandler())
|
Result.success(FakeMediaUploadHandler())
|
||||||
}
|
}
|
||||||
val mediaPreProcessor = FakeMediaPreProcessor().apply {
|
val mediaPreProcessor = FakeMediaPreProcessor().apply {
|
||||||
|
|
@ -343,7 +349,7 @@ class AttachmentsPreviewPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - send video with caption success scenario`() = runTest {
|
fun `present - send video with caption success scenario`() = runTest {
|
||||||
val sendVideoResult =
|
val sendVideoResult =
|
||||||
lambdaRecorder { _: File, _: File?, _: VideoInfo, _: String?, _: String?, _: ProgressCallback?, _: ReplyParameters? ->
|
lambdaRecorder { _: File, _: File?, _: VideoInfo, _: String?, _: String?, _: ProgressCallback?, _: EventId? ->
|
||||||
Result.success(FakeMediaUploadHandler())
|
Result.success(FakeMediaUploadHandler())
|
||||||
}
|
}
|
||||||
val mediaPreProcessor = FakeMediaPreProcessor().apply {
|
val mediaPreProcessor = FakeMediaPreProcessor().apply {
|
||||||
|
|
@ -387,7 +393,7 @@ class AttachmentsPreviewPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - send audio with caption success scenario`() = runTest {
|
fun `present - send audio with caption success scenario`() = runTest {
|
||||||
val sendAudioResult =
|
val sendAudioResult =
|
||||||
lambdaRecorder<File, AudioInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
|
lambdaRecorder<File, AudioInfo, String?, String?, ProgressCallback?, EventId?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
|
||||||
Result.success(FakeMediaUploadHandler())
|
Result.success(FakeMediaUploadHandler())
|
||||||
}
|
}
|
||||||
val mediaPreProcessor = FakeMediaPreProcessor().apply {
|
val mediaPreProcessor = FakeMediaPreProcessor().apply {
|
||||||
|
|
@ -429,7 +435,7 @@ class AttachmentsPreviewPresenterTest {
|
||||||
fun `present - send media failure scenario without media queue`() = runTest {
|
fun `present - send media failure scenario without media queue`() = runTest {
|
||||||
val failure = MediaPreProcessor.Failure(null)
|
val failure = MediaPreProcessor.Failure(null)
|
||||||
val sendFileResult =
|
val sendFileResult =
|
||||||
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
|
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, EventId?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
|
||||||
Result.failure(failure)
|
Result.failure(failure)
|
||||||
}
|
}
|
||||||
val room = FakeJoinedRoom(
|
val room = FakeJoinedRoom(
|
||||||
|
|
@ -460,7 +466,7 @@ class AttachmentsPreviewPresenterTest {
|
||||||
fun `present - send media failure scenario with media queue`() = runTest {
|
fun `present - send media failure scenario with media queue`() = runTest {
|
||||||
val failure = MediaPreProcessor.Failure(null)
|
val failure = MediaPreProcessor.Failure(null)
|
||||||
val sendFileResult =
|
val sendFileResult =
|
||||||
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
|
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, EventId?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
|
||||||
Result.failure(failure)
|
Result.failure(failure)
|
||||||
}
|
}
|
||||||
val onDoneListenerResult = lambdaRecorder<Unit> {}
|
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.RoomMembershipState
|
||||||
import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
|
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.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.TimelineException
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
|
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
|
||||||
|
|
@ -610,7 +609,7 @@ class MessageComposerPresenterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - reply message`() = runTest {
|
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)
|
Result.success(Unit)
|
||||||
}
|
}
|
||||||
val timeline = FakeTimeline().apply {
|
val timeline = FakeTimeline().apply {
|
||||||
|
|
@ -1100,7 +1099,7 @@ class MessageComposerPresenterTest {
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@Test
|
@Test
|
||||||
fun `present - send messages with intentional mentions`() = runTest {
|
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)
|
Result.success(Unit)
|
||||||
}
|
}
|
||||||
val editMessageLambda = lambdaRecorder { _: EventOrTransactionId, _: String, _: String?, _: List<IntentionalMention> ->
|
val editMessageLambda = lambdaRecorder { _: EventOrTransactionId, _: String, _: String?, _: List<IntentionalMention> ->
|
||||||
|
|
|
||||||
|
|
@ -710,11 +710,7 @@ class TimelinePresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - timeline room info includes predecessor room when room has predecessor`() = runTest {
|
fun `present - timeline room info includes predecessor room when room has predecessor`() = runTest {
|
||||||
val predecessorRoomId = RoomId("!predecessor:server.org")
|
val predecessorRoomId = RoomId("!predecessor:server.org")
|
||||||
val predecessorEventId = EventId("\$predecessorEvent:server.org")
|
val predecessorRoom = PredecessorRoom(roomId = predecessorRoomId)
|
||||||
val predecessorRoom = PredecessorRoom(
|
|
||||||
roomId = predecessorRoomId,
|
|
||||||
lastEventId = predecessorEventId
|
|
||||||
)
|
|
||||||
|
|
||||||
val room = FakeJoinedRoom(
|
val room = FakeJoinedRoom(
|
||||||
baseRoom = FakeBaseRoom(
|
baseRoom = FakeBaseRoom(
|
||||||
|
|
@ -730,7 +726,6 @@ class TimelinePresenterTest {
|
||||||
val initialState = awaitFirstItem()
|
val initialState = awaitFirstItem()
|
||||||
assertThat(initialState.timelineRoomInfo.predecessorRoom).isNotNull()
|
assertThat(initialState.timelineRoomInfo.predecessorRoom).isNotNull()
|
||||||
assertThat(initialState.timelineRoomInfo.predecessorRoom?.roomId).isEqualTo(predecessorRoomId)
|
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 im.vector.app.features.analytics.plan.Composer
|
||||||
import io.element.android.features.messages.impl.messagecomposer.aReplyMode
|
import io.element.android.features.messages.impl.messagecomposer.aReplyMode
|
||||||
import io.element.android.features.messages.test.FakeMessageComposerContext
|
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.core.ProgressCallback
|
||||||
import io.element.android.libraries.matrix.api.media.AudioInfo
|
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.media.FakeMediaUploadHandler
|
||||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||||
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
|
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
|
||||||
|
|
@ -63,7 +63,7 @@ class VoiceMessageComposerPresenterTest {
|
||||||
)
|
)
|
||||||
private val analyticsService = FakeAnalyticsService()
|
private val analyticsService = FakeAnalyticsService()
|
||||||
private val sendVoiceMessageResult =
|
private val sendVoiceMessageResult =
|
||||||
lambdaRecorder<File, AudioInfo, List<Float>, ProgressCallback?, ReplyParameters?, Result<FakeMediaUploadHandler>> { _, _, _, _, _ ->
|
lambdaRecorder<File, AudioInfo, List<Float>, ProgressCallback?, EventId?, Result<FakeMediaUploadHandler>> { _, _, _, _, _ ->
|
||||||
Result.success(FakeMediaUploadHandler())
|
Result.success(FakeMediaUploadHandler())
|
||||||
}
|
}
|
||||||
private val joinedRoom = FakeJoinedRoom(
|
private val joinedRoom = FakeJoinedRoom(
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="a11y_polls_will_remove_selection">"Bydd yn dileu\'r dewis blaenorol"</string>
|
||||||
<string name="a11y_polls_winning_answer">"Dyma\'r ateb buddugol"</string>
|
<string name="a11y_polls_winning_answer">"Dyma\'r ateb buddugol"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,6 @@
|
||||||
<item quantity="one">"%1$d Prozent der Stimmen insgesamt"</item>
|
<item quantity="one">"%1$d Prozent der Stimmen insgesamt"</item>
|
||||||
<item quantity="other">"%1$d Prozent der Gesamtstimmen"</item>
|
<item quantity="other">"%1$d Prozent der Gesamtstimmen"</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<string name="a11y_polls_will_remove_selection">"Entfernt die vorherige Auswahl"</string>
|
||||||
<string name="a11y_polls_winning_answer">"Das ist die Gewinnerantwort"</string>
|
<string name="a11y_polls_winning_answer">"Das ist die Gewinnerantwort"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,6 @@
|
||||||
<item quantity="one">"%1$d protsent kõikidest antud häältest"</item>
|
<item quantity="one">"%1$d protsent kõikidest antud häältest"</item>
|
||||||
<item quantity="other">"%1$d protsenti kõikidest antud häältest"</item>
|
<item quantity="other">"%1$d protsenti kõikidest antud häältest"</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<string name="a11y_polls_will_remove_selection">"See kustutab eelmise valiku"</string>
|
||||||
<string name="a11y_polls_winning_answer">"See vastus võitis"</string>
|
<string name="a11y_polls_winning_answer">"See vastus võitis"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,6 @@
|
||||||
<item quantity="one">"%1$d pour cent du total des votes"</item>
|
<item quantity="one">"%1$d pour cent du total des votes"</item>
|
||||||
<item quantity="other">"%1$d pour cent du total des votes"</item>
|
<item quantity="other">"%1$d pour cent du total des votes"</item>
|
||||||
</plurals>
|
</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>
|
<string name="a11y_polls_winning_answer">"C’est la réponse gagnante"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -4,5 +4,6 @@
|
||||||
<item quantity="one">"az összes szavazat %1$d százaléka"</item>
|
<item quantity="one">"az összes szavazat %1$d százaléka"</item>
|
||||||
<item quantity="other">"az összes szavazat %1$d százaléka"</item>
|
<item quantity="other">"az összes szavazat %1$d százaléka"</item>
|
||||||
</plurals>
|
</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>
|
<string name="a11y_polls_winning_answer">"Ez a győztes válasz"</string>
|
||||||
</resources>
|
</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="few">"%1$d percentá z celkového počtu hlasov"</item>
|
||||||
<item quantity="other">"%1$d percent z celkového počtu hlasov"</item>
|
<item quantity="other">"%1$d percent z celkového počtu hlasov"</item>
|
||||||
</plurals>
|
</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>
|
<string name="a11y_polls_winning_answer">"Toto je víťazná odpoveď"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
<string name="screen_create_poll_anonymous_headline">"Ukryj głosy"</string>
|
<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_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_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_desc">"Pytanie lub temat"</string>
|
||||||
<string name="screen_create_poll_question_hint">"Czego dotyczy ankieta?"</string>
|
<string name="screen_create_poll_question_hint">"Czego dotyczy ankieta?"</string>
|
||||||
<string name="screen_create_poll_title">"Utwórz ankietę"</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_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_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">"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_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_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>
|
<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,
|
powerLevel: Long = 0,
|
||||||
normalizedPowerLevel: Long = powerLevel,
|
normalizedPowerLevel: Long = powerLevel,
|
||||||
isIgnored: Boolean = false,
|
isIgnored: Boolean = false,
|
||||||
role: RoomMember.Role = RoomMember.Role.USER,
|
role: RoomMember.Role = RoomMember.Role.User,
|
||||||
membershipChangeReason: String? = null,
|
membershipChangeReason: String? = null,
|
||||||
) = RoomMember(
|
) = RoomMember(
|
||||||
userId = userId,
|
userId = userId,
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,10 @@ import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsV
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
|
|
||||||
internal fun RoomMember.Role.toAnalyticsMemberRole(): RoomModeration.Role = when (this) {
|
internal fun RoomMember.Role.toAnalyticsMemberRole(): RoomModeration.Role = when (this) {
|
||||||
RoomMember.Role.ADMIN -> RoomModeration.Role.Administrator
|
is RoomMember.Role.Owner -> RoomModeration.Role.Administrator // TODO - distinguish creator from admin
|
||||||
RoomMember.Role.MODERATOR -> RoomModeration.Role.Moderator
|
RoomMember.Role.Admin -> RoomModeration.Role.Administrator
|
||||||
RoomMember.Role.USER -> RoomModeration.Role.User
|
RoomMember.Role.Moderator -> RoomModeration.Role.Moderator
|
||||||
|
RoomMember.Role.User -> RoomModeration.Role.User
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun analyticsMemberRoleForPowerLevel(powerLevel: Long): RoomModeration.Role {
|
internal fun analyticsMemberRoleForPowerLevel(powerLevel: Long): RoomModeration.Role {
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ fun aRoomMember(
|
||||||
powerLevel: Long = 0L,
|
powerLevel: Long = 0L,
|
||||||
normalizedPowerLevel: Long = 0L,
|
normalizedPowerLevel: Long = 0L,
|
||||||
isIgnored: Boolean = false,
|
isIgnored: Boolean = false,
|
||||||
role: RoomMember.Role = RoomMember.Role.USER,
|
role: RoomMember.Role = RoomMember.Role.User,
|
||||||
membershipChangeReason: String? = null,
|
membershipChangeReason: String? = null,
|
||||||
) = RoomMember(
|
) = RoomMember(
|
||||||
userId = userId,
|
userId = userId,
|
||||||
|
|
@ -178,8 +178,8 @@ fun aRoomMemberList() = persistentListOf(
|
||||||
aWalter(),
|
aWalter(),
|
||||||
)
|
)
|
||||||
|
|
||||||
fun anAlice() = aRoomMember(UserId("@alice:server.org"), "Alice", role = RoomMember.Role.ADMIN)
|
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 aBob() = aRoomMember(UserId("@bob:server.org"), "Bob", role = RoomMember.Role.Moderator)
|
||||||
|
|
||||||
fun aVictor() = aRoomMember(UserId("@victor:server.org"), "Victor", membership = RoomMembershipState.INVITE)
|
fun aVictor() = aRoomMember(UserId("@victor:server.org"), "Victor", membership = RoomMembershipState.INVITE)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -293,10 +293,12 @@ private fun RoomMemberListItem(
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val roleText = when (roomMemberWithIdentity.roomMember.role) {
|
val member = roomMemberWithIdentity.roomMember
|
||||||
RoomMember.Role.ADMIN -> stringResource(R.string.screen_room_member_list_role_administrator)
|
val roleText = when (member.role) {
|
||||||
RoomMember.Role.MODERATOR -> stringResource(R.string.screen_room_member_list_role_moderator)
|
RoomMember.Role.Admin -> stringResource(R.string.screen_room_member_list_role_administrator)
|
||||||
RoomMember.Role.USER -> null
|
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(
|
MatrixUserRow(
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import io.element.android.anvilannotations.ContributesNode
|
||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
import io.element.android.libraries.matrix.api.room.BaseRoom
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
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.collect
|
||||||
import kotlinx.coroutines.flow.filter
|
import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
|
@ -59,7 +60,8 @@ class RolesAndPermissionsNode @AssistedInject constructor(
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
room.roomInfoFlow
|
room.roomInfoFlow
|
||||||
.filter { info ->
|
.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)
|
.take(1)
|
||||||
.onEach { navigateUp() }
|
.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.RoomMember
|
||||||
import io.element.android.libraries.matrix.api.room.activeRoomMembers
|
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.api.room.powerlevels.UserRoleChange
|
||||||
|
import io.element.android.libraries.matrix.ui.model.roleOf
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
@ -50,14 +51,23 @@ class RolesAndPermissionsPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
val moderatorCount by remember {
|
val moderatorCount by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
roomInfo.userCountWithRole(activeRoomMemberIds, RoomMember.Role.MODERATOR)
|
roomInfo.userCountWithRole(activeRoomMemberIds, RoomMember.Role.Moderator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val adminCount by remember {
|
val adminCount by remember {
|
||||||
derivedStateOf {
|
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 changeOwnRoleAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
|
||||||
val resetPermissionsAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
|
val resetPermissionsAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
|
||||||
|
|
||||||
|
|
@ -83,8 +93,10 @@ class RolesAndPermissionsPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
return RolesAndPermissionsState(
|
return RolesAndPermissionsState(
|
||||||
|
roomSupportsOwnerRole = roomInfo.privilegedCreatorRole,
|
||||||
adminCount = adminCount,
|
adminCount = adminCount,
|
||||||
moderatorCount = moderatorCount,
|
moderatorCount = moderatorCount,
|
||||||
|
canDemoteSelf = canDemoteSelf.value,
|
||||||
changeOwnRoleAction = changeOwnRoleAction.value,
|
changeOwnRoleAction = changeOwnRoleAction.value,
|
||||||
resetPermissionsAction = resetPermissionsAction.value,
|
resetPermissionsAction = resetPermissionsAction.value,
|
||||||
eventSink = { handleEvent(it) },
|
eventSink = { handleEvent(it) },
|
||||||
|
|
@ -110,8 +122,6 @@ class RolesAndPermissionsPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun RoomInfo.userCountWithRole(userIds: List<UserId>, role: RoomMember.Role): Int {
|
private fun RoomInfo.userCountWithRole(userIds: List<UserId>, role: RoomMember.Role): Int {
|
||||||
return this.roomPowerLevels?.users?.count { (userId, level) ->
|
return usersWithRole(role).filter { it in userIds }.size
|
||||||
RoomMember.Role.forPowerLevel(level) == role && userId in userIds
|
|
||||||
} ?: 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,10 @@ package io.element.android.features.roomdetails.impl.rolesandpermissions
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
|
|
||||||
data class RolesAndPermissionsState(
|
data class RolesAndPermissionsState(
|
||||||
|
val roomSupportsOwnerRole: Boolean,
|
||||||
val adminCount: Int,
|
val adminCount: Int,
|
||||||
val moderatorCount: Int,
|
val moderatorCount: Int,
|
||||||
|
val canDemoteSelf: Boolean,
|
||||||
val changeOwnRoleAction: AsyncAction<Unit>,
|
val changeOwnRoleAction: AsyncAction<Unit>,
|
||||||
val resetPermissionsAction: AsyncAction<Unit>,
|
val resetPermissionsAction: AsyncAction<Unit>,
|
||||||
val eventSink: (RolesAndPermissionsEvents) -> Unit,
|
val eventSink: (RolesAndPermissionsEvents) -> Unit,
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import io.element.android.libraries.architecture.AsyncAction
|
||||||
class RolesAndPermissionsStateProvider : PreviewParameterProvider<RolesAndPermissionsState> {
|
class RolesAndPermissionsStateProvider : PreviewParameterProvider<RolesAndPermissionsState> {
|
||||||
override val values: Sequence<RolesAndPermissionsState>
|
override val values: Sequence<RolesAndPermissionsState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aRolesAndPermissionsState(),
|
aRolesAndPermissionsState(roomSupportsOwners = false),
|
||||||
aRolesAndPermissionsState(adminCount = 1, moderatorCount = 2),
|
aRolesAndPermissionsState(adminCount = 1, moderatorCount = 2),
|
||||||
aRolesAndPermissionsState(
|
aRolesAndPermissionsState(
|
||||||
adminCount = 1,
|
adminCount = 1,
|
||||||
|
|
@ -45,17 +45,22 @@ class RolesAndPermissionsStateProvider : PreviewParameterProvider<RolesAndPermis
|
||||||
moderatorCount = 2,
|
moderatorCount = 2,
|
||||||
resetPermissionsAction = AsyncAction.Failure(IllegalStateException("Failed to reset permissions")),
|
resetPermissionsAction = AsyncAction.Failure(IllegalStateException("Failed to reset permissions")),
|
||||||
),
|
),
|
||||||
|
aRolesAndPermissionsState(canDemoteSelf = false),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun aRolesAndPermissionsState(
|
internal fun aRolesAndPermissionsState(
|
||||||
|
roomSupportsOwners: Boolean = true,
|
||||||
adminCount: Int = 0,
|
adminCount: Int = 0,
|
||||||
moderatorCount: Int = 0,
|
moderatorCount: Int = 0,
|
||||||
|
canDemoteSelf: Boolean = true,
|
||||||
changeOwnRoleAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
changeOwnRoleAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
resetPermissionsAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
resetPermissionsAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
eventSink: (RolesAndPermissionsEvents) -> Unit = {},
|
eventSink: (RolesAndPermissionsEvents) -> Unit = {},
|
||||||
) = RolesAndPermissionsState(
|
) = RolesAndPermissionsState(
|
||||||
|
roomSupportsOwnerRole = roomSupportsOwners,
|
||||||
adminCount = adminCount,
|
adminCount = adminCount,
|
||||||
|
canDemoteSelf = canDemoteSelf,
|
||||||
moderatorCount = moderatorCount,
|
moderatorCount = moderatorCount,
|
||||||
changeOwnRoleAction = changeOwnRoleAction,
|
changeOwnRoleAction = changeOwnRoleAction,
|
||||||
resetPermissionsAction = resetPermissionsAction,
|
resetPermissionsAction = resetPermissionsAction,
|
||||||
|
|
|
||||||
|
|
@ -55,8 +55,14 @@ fun RolesAndPermissionsView(
|
||||||
onBackClick = rolesAndPermissionsNavigator::onBackClick,
|
onBackClick = rolesAndPermissionsNavigator::onBackClick,
|
||||||
) {
|
) {
|
||||||
ListSectionHeader(title = stringResource(R.string.screen_room_roles_and_permissions_roles_header), hasDivider = false)
|
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(
|
ListItem(
|
||||||
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_admins)) },
|
headlineContent = { Text(adminsTitle) },
|
||||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Admin())),
|
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Admin())),
|
||||||
trailingContent = ListItemContent.Text("${state.adminCount}"),
|
trailingContent = ListItemContent.Text("${state.adminCount}"),
|
||||||
onClick = { rolesAndPermissionsNavigator.openAdminList() },
|
onClick = { rolesAndPermissionsNavigator.openAdminList() },
|
||||||
|
|
@ -67,11 +73,13 @@ fun RolesAndPermissionsView(
|
||||||
trailingContent = ListItemContent.Text("${state.moderatorCount}"),
|
trailingContent = ListItemContent.Text("${state.moderatorCount}"),
|
||||||
onClick = { rolesAndPermissionsNavigator.openModeratorList() },
|
onClick = { rolesAndPermissionsNavigator.openModeratorList() },
|
||||||
)
|
)
|
||||||
ListItem(
|
if (state.canDemoteSelf) {
|
||||||
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_change_my_role)) },
|
ListItem(
|
||||||
onClick = { state.eventSink(RolesAndPermissionsEvents.ChangeOwnRole) },
|
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_change_my_role)) },
|
||||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Edit()))
|
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)
|
ListSectionHeader(title = stringResource(R.string.screen_room_roles_and_permissions_permissions_header), hasDivider = true)
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_room_details)) },
|
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)) },
|
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_change_role_demote_to_moderator)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
sheetState.hide(coroutineScope) {
|
sheetState.hide(coroutineScope) {
|
||||||
eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.MODERATOR))
|
eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.Moderator))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
style = ListItemStyle.Destructive,
|
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)) },
|
headlineContent = { Text(stringResource(R.string.screen_room_roles_and_permissions_change_role_demote_to_member)) },
|
||||||
onClick = {
|
onClick = {
|
||||||
sheetState.hide(coroutineScope) {
|
sheetState.hide(coroutineScope) {
|
||||||
eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.USER))
|
eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.User))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
style = ListItemStyle.Destructive,
|
style = ListItemStyle.Destructive,
|
||||||
|
|
|
||||||
|
|
@ -44,8 +44,8 @@ class ChangeRolesNode @AssistedInject constructor(
|
||||||
|
|
||||||
private val presenter = presenterFactory.run {
|
private val presenter = presenterFactory.run {
|
||||||
val role = when (inputs.listType) {
|
val role = when (inputs.listType) {
|
||||||
is ListType.Admins -> RoomMember.Role.ADMIN
|
is ListType.Admins -> RoomMember.Role.Admin
|
||||||
is ListType.Moderators -> RoomMember.Role.MODERATOR
|
is ListType.Moderators -> RoomMember.Role.Moderator
|
||||||
}
|
}
|
||||||
create(role)
|
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.powerlevels.usersWithRole
|
||||||
import io.element.android.libraries.matrix.api.room.toMatrixUser
|
import io.element.android.libraries.matrix.api.room.toMatrixUser
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
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 io.element.android.services.analytics.api.AnalyticsService
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.PersistentList
|
import kotlinx.collections.immutable.PersistentList
|
||||||
|
|
@ -74,18 +75,17 @@ class ChangeRolesPresenter @AssistedInject constructor(
|
||||||
val exitState: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
val exitState: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||||
val saveState: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
val saveState: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||||
val usersWithRole = produceState(initialValue = persistentListOf()) {
|
val usersWithRole = produceState(initialValue = persistentListOf()) {
|
||||||
room.usersWithRole(role)
|
room.usersWithRole(role).map { members -> members.map { it.toMatrixUser() } }
|
||||||
.map { members -> members.map { it.toMatrixUser() } }
|
.onEach { users ->
|
||||||
.onEach { users ->
|
val previous: PersistentList<MatrixUser> = value
|
||||||
val previous: PersistentList<MatrixUser> = value
|
value = users.toPersistentList()
|
||||||
value = users.toPersistentList()
|
// Users who were selected but didn't have the role, so their role change was pending
|
||||||
// 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 } }
|
||||||
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
|
||||||
// Users who no longer have the role
|
val toRemove = previous.filter { user -> users.none { it.userId == user.userId } }.toSet()
|
||||||
val toRemove = previous.filter { user -> users.none { it.userId == user.userId } }.toSet()
|
selectedUsers.value = (users + toAdd - toRemove).toImmutableList()
|
||||||
selectedUsers.value = (users + toAdd - toRemove).toImmutableList()
|
}
|
||||||
}
|
.launchIn(this)
|
||||||
.launchIn(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val roomMemberState by room.membersStateFlow.collectAsState()
|
val roomMemberState by room.membersStateFlow.collectAsState()
|
||||||
|
|
@ -96,7 +96,6 @@ class ChangeRolesPresenter @AssistedInject constructor(
|
||||||
.search(query.orEmpty())
|
.search(query.orEmpty())
|
||||||
.groupedByRole()
|
.groupedByRole()
|
||||||
|
|
||||||
println(results)
|
|
||||||
searchResults = if (results.isEmpty()) {
|
searchResults = if (results.isEmpty()) {
|
||||||
SearchBarResultState.NoResultsFound()
|
SearchBarResultState.NoResultsFound()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -108,9 +107,10 @@ class ChangeRolesPresenter @AssistedInject constructor(
|
||||||
|
|
||||||
val roomInfo by room.roomInfoFlow.collectAsState()
|
val roomInfo by room.roomInfoFlow.collectAsState()
|
||||||
fun canChangeMemberRole(userId: UserId): Boolean {
|
fun canChangeMemberRole(userId: UserId): Boolean {
|
||||||
// An admin can't remove or demote another admin
|
// This is used to group the
|
||||||
val powerLevel = roomInfo.roomPowerLevels?.users?.get(userId) ?: 0L
|
val currentUserRole = roomInfo.roleOf(room.sessionId)
|
||||||
return RoomMember.Role.forPowerLevel(powerLevel) != RoomMember.Role.ADMIN
|
val otherUserRole = roomInfo.roleOf(userId)
|
||||||
|
return currentUserRole.powerLevel > otherUserRole.powerLevel
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleEvent(event: ChangeRolesEvent) {
|
fun handleEvent(event: ChangeRolesEvent) {
|
||||||
|
|
@ -132,11 +132,21 @@ class ChangeRolesPresenter @AssistedInject constructor(
|
||||||
selectedUsers.value = newList.toImmutableList()
|
selectedUsers.value = newList.toImmutableList()
|
||||||
}
|
}
|
||||||
is ChangeRolesEvent.Save -> {
|
is ChangeRolesEvent.Save -> {
|
||||||
if (role == RoomMember.Role.ADMIN && selectedUsers != usersWithRole && !saveState.value.isConfirming()) {
|
val currentUserIsAdmin = roomInfo.roleOf(room.sessionId) == RoomMember.Role.Admin
|
||||||
// Confirm adding admin
|
val isModifyingAdmins = role == RoomMember.Role.Admin
|
||||||
saveState.value = AsyncAction.ConfirmingNoParams
|
val hasChanges = selectedUsers != usersWithRole
|
||||||
} else if (!saveState.value.isLoading()) {
|
val isConfirming = saveState.value.isConfirming()
|
||||||
coroutineScope.save(usersWithRole.value, selectedUsers, saveState)
|
|
||||||
|
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 -> {
|
is ChangeRolesEvent.ClearError -> {
|
||||||
|
|
@ -174,10 +184,12 @@ class ChangeRolesPresenter @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<RoomMember>.groupedByRole(): MembersByRole {
|
private fun List<RoomMember>.groupedByRole(): MembersByRole {
|
||||||
|
val groupedMembers = MembersByRole(this)
|
||||||
return MembersByRole(
|
return MembersByRole(
|
||||||
admins = filter { it.role == RoomMember.Role.ADMIN }.sorted(),
|
owners = groupedMembers.owners.sorted(),
|
||||||
moderators = filter { it.role == RoomMember.Role.MODERATOR }.sorted(),
|
admins = groupedMembers.admins.sorted(),
|
||||||
members = filter { it.role == RoomMember.Role.USER }.sorted(),
|
moderators = groupedMembers.moderators.sorted(),
|
||||||
|
members = groupedMembers.members.sorted(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,7 +214,7 @@ class ChangeRolesPresenter @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
for (selectedUser in toRemove) {
|
for (selectedUser in toRemove) {
|
||||||
analyticsService.capture(RoomModeration(RoomModeration.Action.ChangeMemberRole, RoomModeration.Role.User))
|
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(
|
data class MembersByRole(
|
||||||
|
val owners: ImmutableList<RoomMember>,
|
||||||
val admins: ImmutableList<RoomMember>,
|
val admins: ImmutableList<RoomMember>,
|
||||||
val moderators: ImmutableList<RoomMember>,
|
val moderators: ImmutableList<RoomMember>,
|
||||||
val members: ImmutableList<RoomMember>,
|
val members: ImmutableList<RoomMember>,
|
||||||
) {
|
) {
|
||||||
constructor(members: List<RoomMember>) : this(
|
constructor(members: List<RoomMember>) : this(
|
||||||
admins = members.filter { it.role == RoomMember.Role.ADMIN }.sorted(),
|
owners = members.filter { it.role is RoomMember.Role.Owner }.sorted(),
|
||||||
moderators = members.filter { it.role == RoomMember.Role.MODERATOR }.sorted(),
|
admins = members.filter { it.role == RoomMember.Role.Admin }.sorted(),
|
||||||
members = members.filter { it.role == RoomMember.Role.USER }.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> {
|
private fun Iterable<RoomMember>.sorted(): ImmutableList<RoomMember> {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
package io.element.android.features.roomdetails.impl.rolesandpermissions.changeroles
|
package io.element.android.features.roomdetails.impl.rolesandpermissions.changeroles
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
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.features.roomdetails.impl.members.aRoomMemberList
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
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.RoomMember
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
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 io.element.android.libraries.matrix.ui.components.aMatrixUserList
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
@ -24,7 +26,7 @@ class ChangeRolesStateProvider : PreviewParameterProvider<ChangeRolesState> {
|
||||||
override val values: Sequence<ChangeRolesState>
|
override val values: Sequence<ChangeRolesState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aChangeRolesState(),
|
aChangeRolesState(),
|
||||||
aChangeRolesStateWithSelectedUsers().copy(role = RoomMember.Role.MODERATOR),
|
aChangeRolesStateWithSelectedUsers().copy(role = RoomMember.Role.Moderator),
|
||||||
aChangeRolesStateWithSelectedUsers().copy(hasPendingChanges = false),
|
aChangeRolesStateWithSelectedUsers().copy(hasPendingChanges = false),
|
||||||
aChangeRolesStateWithSelectedUsers(),
|
aChangeRolesStateWithSelectedUsers(),
|
||||||
aChangeRolesStateWithSelectedUsers().copy(
|
aChangeRolesStateWithSelectedUsers().copy(
|
||||||
|
|
@ -41,11 +43,12 @@ class ChangeRolesStateProvider : PreviewParameterProvider<ChangeRolesState> {
|
||||||
aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.Loading),
|
aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.Loading),
|
||||||
aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.Success(Unit)),
|
aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.Success(Unit)),
|
||||||
aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.Failure(Exception("boom"))),
|
aChangeRolesStateWithSelectedUsers().copy(savingState = AsyncAction.Failure(Exception("boom"))),
|
||||||
|
aChangeRolesStateWithOwners(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun aChangeRolesState(
|
internal fun aChangeRolesState(
|
||||||
role: RoomMember.Role = RoomMember.Role.ADMIN,
|
role: RoomMember.Role = RoomMember.Role.Admin,
|
||||||
query: String? = null,
|
query: String? = null,
|
||||||
isSearchActive: Boolean = false,
|
isSearchActive: Boolean = false,
|
||||||
searchResults: SearchBarResultState<MembersByRole> = SearchBarResultState.NoResultsFound(),
|
searchResults: SearchBarResultState<MembersByRole> = SearchBarResultState.NoResultsFound(),
|
||||||
|
|
@ -84,3 +87,47 @@ internal fun aChangeRolesStateWithSelectedUsers() = aChangeRolesState(
|
||||||
hasPendingChanges = true,
|
hasPendingChanges = true,
|
||||||
canRemoveMember = { it != UserId("@alice:server.org") },
|
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.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Checkbox
|
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.Scaffold
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBar
|
import io.element.android.libraries.designsystem.theme.components.SearchBar
|
||||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||||
|
|
@ -96,9 +97,9 @@ fun ChangeRolesView(
|
||||||
AnimatedVisibility(visible = !state.isSearchActive) {
|
AnimatedVisibility(visible = !state.isSearchActive) {
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
titleStr = when (state.role) {
|
titleStr = when (state.role) {
|
||||||
RoomMember.Role.ADMIN -> stringResource(R.string.screen_room_change_role_administrators_title)
|
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.Moderator -> stringResource(R.string.screen_room_change_role_moderators_title)
|
||||||
RoomMember.Role.USER -> error("This should never be reached")
|
is RoomMember.Role.Owner, RoomMember.Role.User -> error("This should never be reached")
|
||||||
},
|
},
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
BackButton(onClick = { state.eventSink(ChangeRolesEvent.Exit) })
|
BackButton(onClick = { state.eventSink(ChangeRolesEvent.Exit) })
|
||||||
|
|
@ -187,7 +188,7 @@ fun ChangeRolesView(
|
||||||
|
|
||||||
when (state.savingState) {
|
when (state.savingState) {
|
||||||
is AsyncAction.Confirming -> {
|
is AsyncAction.Confirming -> {
|
||||||
if (state.role == RoomMember.Role.ADMIN) {
|
if (state.role == RoomMember.Role.Admin) {
|
||||||
// Confirm adding new admins dialogs
|
// Confirm adding new admins dialogs
|
||||||
ConfirmationDialog(
|
ConfirmationDialog(
|
||||||
title = stringResource(R.string.screen_room_change_role_confirm_add_admin_title),
|
title = stringResource(R.string.screen_room_change_role_confirm_add_admin_title),
|
||||||
|
|
@ -234,10 +235,30 @@ private fun SearchResultsList(
|
||||||
item {
|
item {
|
||||||
selectedUsersList(selectedUsers)
|
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()) {
|
if (searchResults.admins.isNotEmpty()) {
|
||||||
stickyHeader { ListSectionHeader(text = stringResource(R.string.screen_room_roles_and_permissions_admins)) }
|
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
|
// Add a footer for the admin section in change role to moderator screen
|
||||||
if (currentRole == RoomMember.Role.MODERATOR) {
|
if (currentRole == RoomMember.Role.Moderator) {
|
||||||
item {
|
item {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -303,20 +324,24 @@ private fun ListMemberItem(
|
||||||
) {
|
) {
|
||||||
val canToggle = canRemoveMember(roomMember.userId)
|
val canToggle = canRemoveMember(roomMember.userId)
|
||||||
val trailingContent: @Composable (() -> Unit) = {
|
val trailingContent: @Composable (() -> Unit) = {
|
||||||
Checkbox(
|
if (canToggle) {
|
||||||
checked = selectedUsers.any { it.userId == roomMember.userId },
|
Checkbox(
|
||||||
onCheckedChange = { onToggleSelection(roomMember) },
|
checked = selectedUsers.any { it.userId == roomMember.userId },
|
||||||
enabled = canToggle,
|
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
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -55,15 +55,15 @@ internal fun aChangeRoomPermissionsState(
|
||||||
private fun previewPermissions(): RoomPowerLevelsValues {
|
private fun previewPermissions(): RoomPowerLevelsValues {
|
||||||
return RoomPowerLevelsValues(
|
return RoomPowerLevelsValues(
|
||||||
// MembershipModeration section
|
// MembershipModeration section
|
||||||
invite = RoomMember.Role.ADMIN.powerLevel,
|
invite = RoomMember.Role.Admin.powerLevel,
|
||||||
kick = RoomMember.Role.MODERATOR.powerLevel,
|
kick = RoomMember.Role.Moderator.powerLevel,
|
||||||
ban = RoomMember.Role.USER.powerLevel,
|
ban = RoomMember.Role.User.powerLevel,
|
||||||
// MessagesAndContent section
|
// MessagesAndContent section
|
||||||
redactEvents = RoomMember.Role.MODERATOR.powerLevel,
|
redactEvents = RoomMember.Role.Moderator.powerLevel,
|
||||||
sendEvents = RoomMember.Role.ADMIN.powerLevel,
|
sendEvents = RoomMember.Role.Admin.powerLevel,
|
||||||
// RoomDetails section
|
// RoomDetails section
|
||||||
roomName = RoomMember.Role.ADMIN.powerLevel,
|
roomName = RoomMember.Role.Admin.powerLevel,
|
||||||
roomAvatar = RoomMember.Role.MODERATOR.powerLevel,
|
roomAvatar = RoomMember.Role.Moderator.powerLevel,
|
||||||
roomTopic = RoomMember.Role.USER.powerLevel,
|
roomTopic = RoomMember.Role.User.powerLevel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -80,21 +80,21 @@ fun ChangeRoomPermissionsView(
|
||||||
ListSectionHeader(titleForSection(item = permissionItem), hasDivider = index > 0)
|
ListSectionHeader(titleForSection(item = permissionItem), hasDivider = index > 0)
|
||||||
SelectRoleItem(
|
SelectRoleItem(
|
||||||
permissionsItem = permissionItem,
|
permissionsItem = permissionItem,
|
||||||
role = RoomMember.Role.ADMIN,
|
role = RoomMember.Role.Admin,
|
||||||
currentPermissions = state.currentPermissions
|
currentPermissions = state.currentPermissions
|
||||||
) { item, role ->
|
) { item, role ->
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role))
|
||||||
}
|
}
|
||||||
SelectRoleItem(
|
SelectRoleItem(
|
||||||
permissionsItem = permissionItem,
|
permissionsItem = permissionItem,
|
||||||
role = RoomMember.Role.MODERATOR,
|
role = RoomMember.Role.Moderator,
|
||||||
currentPermissions = state.currentPermissions
|
currentPermissions = state.currentPermissions
|
||||||
) { item, role ->
|
) { item, role ->
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role))
|
||||||
}
|
}
|
||||||
SelectRoleItem(
|
SelectRoleItem(
|
||||||
permissionsItem = permissionItem,
|
permissionsItem = permissionItem,
|
||||||
role = RoomMember.Role.USER,
|
role = RoomMember.Role.User,
|
||||||
currentPermissions = state.currentPermissions
|
currentPermissions = state.currentPermissions
|
||||||
) { item, role ->
|
) { item, role ->
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(item, role))
|
||||||
|
|
@ -135,9 +135,10 @@ private fun SelectRoleItem(
|
||||||
onClick: (RoomPermissionType, RoomMember.Role) -> Unit
|
onClick: (RoomPermissionType, RoomMember.Role) -> Unit
|
||||||
) {
|
) {
|
||||||
val title = when (role) {
|
val title = when (role) {
|
||||||
RoomMember.Role.ADMIN -> stringResource(R.string.screen_room_change_permissions_administrators)
|
RoomMember.Role.Admin -> stringResource(R.string.screen_room_change_permissions_administrators)
|
||||||
RoomMember.Role.MODERATOR -> stringResource(R.string.screen_room_change_permissions_moderators)
|
RoomMember.Role.Moderator -> stringResource(R.string.screen_room_change_permissions_moderators)
|
||||||
RoomMember.Role.USER -> stringResource(R.string.screen_room_change_permissions_everyone)
|
RoomMember.Role.User -> stringResource(R.string.screen_room_change_permissions_everyone)
|
||||||
|
else -> error("Unsupported role selected: $role")
|
||||||
}
|
}
|
||||||
ListItem(
|
ListItem(
|
||||||
headlineContent = { Text(text = title) },
|
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_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_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_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_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_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>
|
<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_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_encryption_enabled_title">"Įjungtas žinučių šifravimas"</string>
|
||||||
<string name="screen_room_details_invite_people_title">"Pakviesti žmonių"</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_leave_room_title">"Palikti kambarį"</string>
|
||||||
<string name="screen_room_details_room_name_label">"Kambario pavadinimas"</string>
|
<string name="screen_room_details_room_name_label">"Kambario pavadinimas"</string>
|
||||||
<string name="screen_room_details_security_title">"Saugumas"</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_pending_header_title">"Čaká sa"</string>
|
||||||
<string name="screen_room_member_list_role_administrator">"Administrátor"</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_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_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_member_list_unbanning_user">"Zrušenie zákazu %1$s"</string>
|
||||||
<string name="screen_room_notification_settings_allow_custom">"Povoliť vlastné nastavenie"</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">"%1$s (Pending)"</string>
|
||||||
<string name="screen_room_change_role_invited_member_name_android">"(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_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_moderators_title">"Edit Moderators"</string>
|
||||||
<string name="screen_room_change_role_section_administrators">"Admins"</string>
|
<string name="screen_room_change_role_section_administrators">"Admins"</string>
|
||||||
<string name="screen_room_change_role_section_moderators">"Moderators"</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_pending_header_title">"Pending"</string>
|
||||||
<string name="screen_room_member_list_role_administrator">"Admin"</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_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_room_members_header_title">"Room members"</string>
|
||||||
<string name="screen_room_member_list_unbanning_user">"Unbanning %1$s"</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>
|
<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_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_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">"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_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_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_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_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_messages_and_content">"Messages and content"</string>
|
||||||
<string name="screen_room_roles_and_permissions_moderators">"Moderators"</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_permissions_header">"Permissions"</string>
|
||||||
<string name="screen_room_roles_and_permissions_reset">"Reset 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>
|
<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()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.MODERATOR))
|
initialState.eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.Moderator))
|
||||||
|
|
||||||
runCurrent()
|
runCurrent()
|
||||||
assertThat(awaitItem().changeOwnRoleAction).isEqualTo(AsyncAction.Loading)
|
assertThat(awaitItem().changeOwnRoleAction).isEqualTo(AsyncAction.Loading)
|
||||||
|
|
@ -87,7 +87,7 @@ class RolesAndPermissionPresenterTest {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.MODERATOR))
|
initialState.eventSink(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.Moderator))
|
||||||
|
|
||||||
runCurrent()
|
runCurrent()
|
||||||
assertThat(awaitItem().changeOwnRoleAction).isEqualTo(AsyncAction.Loading)
|
assertThat(awaitItem().changeOwnRoleAction).isEqualTo(AsyncAction.Loading)
|
||||||
|
|
|
||||||
|
|
@ -47,12 +47,30 @@ class RolesAndPermissionsViewTest {
|
||||||
fun `tapping on Admins opens admin list`() {
|
fun `tapping on Admins opens admin list`() {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setRolesAndPermissionsView(
|
rule.setRolesAndPermissionsView(
|
||||||
|
aRolesAndPermissionsState(
|
||||||
|
roomSupportsOwners = false,
|
||||||
|
eventSink = EventsRecorder(expectEvents = false)
|
||||||
|
),
|
||||||
openAdminList = callback,
|
openAdminList = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_room_roles_and_permissions_admins)
|
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
|
@Test
|
||||||
fun `tapping on Moderators opens moderators list`() {
|
fun `tapping on Moderators opens moderators list`() {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
|
|
@ -126,7 +144,7 @@ class RolesAndPermissionsViewTest {
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_room_roles_and_permissions_change_role_demote_to_moderator)
|
rule.clickOn(R.string.screen_room_roles_and_permissions_change_role_demote_to_moderator)
|
||||||
rule.mainClock.advanceTimeBy(1_000L)
|
rule.mainClock.advanceTimeBy(1_000L)
|
||||||
recorder.assertSingle(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.MODERATOR))
|
recorder.assertSingle(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.Moderator))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -140,7 +158,7 @@ class RolesAndPermissionsViewTest {
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_room_roles_and_permissions_change_role_demote_to_member)
|
rule.clickOn(R.string.screen_room_roles_and_permissions_change_role_demote_to_member)
|
||||||
rule.mainClock.advanceTimeBy(1_000L)
|
rule.mainClock.advanceTimeBy(1_000L)
|
||||||
recorder.assertSingle(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.USER))
|
recorder.assertSingle(RolesAndPermissionsEvents.DemoteSelfTo(RoomMember.Role.User))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -160,6 +178,7 @@ class RolesAndPermissionsViewTest {
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRolesAndPermissionsView(
|
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRolesAndPermissionsView(
|
||||||
state: RolesAndPermissionsState = aRolesAndPermissionsState(
|
state: RolesAndPermissionsState = aRolesAndPermissionsState(
|
||||||
|
roomSupportsOwners = false,
|
||||||
eventSink = EventsRecorder(expectEvents = false),
|
eventSink = EventsRecorder(expectEvents = false),
|
||||||
),
|
),
|
||||||
goBack: () -> Unit = EnsureNeverCalled(),
|
goBack: () -> Unit = EnsureNeverCalled(),
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import im.vector.app.features.analytics.plan.RoomModeration
|
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.features.roomdetails.impl.members.aRoomMemberList
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
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.api.user.MatrixUser
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
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_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.FakeBaseRoom
|
||||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||||
|
|
@ -43,7 +45,7 @@ class ChangeRolesPresenterTest {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
with(awaitItem()) {
|
with(awaitItem()) {
|
||||||
assertThat(role).isEqualTo(RoomMember.Role.ADMIN)
|
assertThat(role).isEqualTo(RoomMember.Role.Admin)
|
||||||
assertThat(query).isNull()
|
assertThat(query).isNull()
|
||||||
assertThat(isSearchActive).isFalse()
|
assertThat(isSearchActive).isFalse()
|
||||||
assertThat(searchResults).isInstanceOf(SearchBarResultState.Initial::class.java)
|
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
|
@Test
|
||||||
fun `present - ToggleSearchActive changes the value`() = runTest {
|
fun `present - ToggleSearchActive changes the value`() = runTest {
|
||||||
val room = FakeJoinedRoom().apply {
|
val room = FakeJoinedRoom().apply {
|
||||||
|
|
@ -145,7 +217,7 @@ class ChangeRolesPresenterTest {
|
||||||
fun `present - UserSelectionToggle adds and removes users from the selected user list`() = runTest {
|
fun `present - UserSelectionToggle adds and removes users from the selected user list`() = runTest {
|
||||||
val room = FakeJoinedRoom().apply {
|
val room = FakeJoinedRoom().apply {
|
||||||
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
||||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.ADMIN)))
|
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin)))
|
||||||
}
|
}
|
||||||
val presenter = createChangeRolesPresenter(room = room)
|
val presenter = createChangeRolesPresenter(room = room)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
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 {
|
fun `present - hasPendingChanges is true when the initial selected users don't match the new ones`() = runTest {
|
||||||
val room = FakeJoinedRoom().apply {
|
val room = FakeJoinedRoom().apply {
|
||||||
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
||||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.ADMIN)))
|
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin)))
|
||||||
}
|
}
|
||||||
val presenter = createChangeRolesPresenter(room = room)
|
val presenter = createChangeRolesPresenter(room = room)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
|
@ -196,7 +268,7 @@ class ChangeRolesPresenterTest {
|
||||||
fun `present - Exit will display success if no pending changes`() = runTest {
|
fun `present - Exit will display success if no pending changes`() = runTest {
|
||||||
val room = FakeJoinedRoom().apply {
|
val room = FakeJoinedRoom().apply {
|
||||||
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
||||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.ADMIN)))
|
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin)))
|
||||||
}
|
}
|
||||||
val presenter = createChangeRolesPresenter(room = room)
|
val presenter = createChangeRolesPresenter(room = room)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
|
@ -216,7 +288,7 @@ class ChangeRolesPresenterTest {
|
||||||
fun `present - CancelExit will remove exit confirmation`() = runTest {
|
fun `present - CancelExit will remove exit confirmation`() = runTest {
|
||||||
val room = FakeJoinedRoom().apply {
|
val room = FakeJoinedRoom().apply {
|
||||||
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
||||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.ADMIN)))
|
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin)))
|
||||||
}
|
}
|
||||||
val presenter = createChangeRolesPresenter(room = room)
|
val presenter = createChangeRolesPresenter(room = room)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
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 {
|
fun `present - Exit will display a confirmation dialog if there are pending changes, calling it again will actually exit`() = runTest {
|
||||||
val room = FakeJoinedRoom().apply {
|
val room = FakeJoinedRoom().apply {
|
||||||
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
||||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.ADMIN)))
|
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Admin)))
|
||||||
}
|
}
|
||||||
val presenter = createChangeRolesPresenter(room = room)
|
val presenter = createChangeRolesPresenter(room = room)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
|
|
@ -273,9 +345,9 @@ class ChangeRolesPresenterTest {
|
||||||
baseRoom = FakeBaseRoom(updateMembersResult = { Result.success(Unit) }),
|
baseRoom = FakeBaseRoom(updateMembersResult = { Result.success(Unit) }),
|
||||||
).apply {
|
).apply {
|
||||||
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
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) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -302,9 +374,9 @@ class ChangeRolesPresenterTest {
|
||||||
fun `present - CancelSave will remove the confirmation dialog`() = runTest {
|
fun `present - CancelSave will remove the confirmation dialog`() = runTest {
|
||||||
val room = FakeJoinedRoom().apply {
|
val room = FakeJoinedRoom().apply {
|
||||||
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
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) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -331,10 +403,10 @@ class ChangeRolesPresenterTest {
|
||||||
baseRoom = FakeBaseRoom(updateMembersResult = { Result.success(Unit) }),
|
baseRoom = FakeBaseRoom(updateMembersResult = { Result.success(Unit) }),
|
||||||
).apply {
|
).apply {
|
||||||
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
||||||
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.MODERATOR)))
|
givenRoomInfo(aRoomInfo(roomPowerLevels = roomPowerLevelsWithRole(RoomMember.Role.Moderator)))
|
||||||
}
|
}
|
||||||
val presenter = createChangeRolesPresenter(
|
val presenter = createChangeRolesPresenter(
|
||||||
role = RoomMember.Role.MODERATOR,
|
role = RoomMember.Role.Moderator,
|
||||||
room = room,
|
room = room,
|
||||||
analyticsService = analyticsService
|
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
|
@Test
|
||||||
fun `present - Save can handle failures and ClearError clears them`() = runTest {
|
fun `present - Save can handle failures and ClearError clears them`() = runTest {
|
||||||
val room = FakeJoinedRoom(
|
val room = FakeJoinedRoom(
|
||||||
updateUserRoleResult = { Result.failure(IllegalStateException("Failed")) }
|
updateUserRoleResult = { Result.failure(IllegalStateException("Failed")) }
|
||||||
).apply {
|
).apply {
|
||||||
givenRoomMembersState(RoomMembersState.Ready(aRoomMemberList()))
|
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) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
|
|
@ -399,7 +511,7 @@ class ChangeRolesPresenterTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun TestScope.createChangeRolesPresenter(
|
private fun TestScope.createChangeRolesPresenter(
|
||||||
role: RoomMember.Role = RoomMember.Role.ADMIN,
|
role: RoomMember.Role = RoomMember.Role.Admin,
|
||||||
room: FakeJoinedRoom = FakeJoinedRoom(),
|
room: FakeJoinedRoom = FakeJoinedRoom(),
|
||||||
dispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
|
dispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
|
||||||
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
|
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
|
||||||
|
|
|
||||||
|
|
@ -41,11 +41,25 @@ class ChangeRolesViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `passing a 'USER' role throws an exception`() {
|
fun `passing a 'User' role throws an exception`() {
|
||||||
val exception = runCatchingExceptions {
|
val exception = runCatchingExceptions {
|
||||||
rule.setChangeRolesContent(
|
rule.setChangeRolesContent(
|
||||||
state = aChangeRolesState(
|
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(),
|
eventSink = EnsureNeverCalledWithParam(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -166,7 +180,7 @@ class ChangeRolesViewTest {
|
||||||
val eventsRecorder = EventsRecorder<ChangeRolesEvent>()
|
val eventsRecorder = EventsRecorder<ChangeRolesEvent>()
|
||||||
rule.setChangeRolesContent(
|
rule.setChangeRolesContent(
|
||||||
state = aChangeRolesState(
|
state = aChangeRolesState(
|
||||||
role = RoomMember.Role.ADMIN,
|
role = RoomMember.Role.Admin,
|
||||||
isSearchActive = true,
|
isSearchActive = true,
|
||||||
savingState = AsyncAction.ConfirmingNoParams,
|
savingState = AsyncAction.ConfirmingNoParams,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
|
|
@ -183,7 +197,7 @@ class ChangeRolesViewTest {
|
||||||
val eventsRecorder = EventsRecorder<ChangeRolesEvent>()
|
val eventsRecorder = EventsRecorder<ChangeRolesEvent>()
|
||||||
rule.setChangeRolesContent(
|
rule.setChangeRolesContent(
|
||||||
state = aChangeRolesState(
|
state = aChangeRolesState(
|
||||||
role = RoomMember.Role.ADMIN,
|
role = RoomMember.Role.Admin,
|
||||||
isSearchActive = true,
|
isSearchActive = true,
|
||||||
savingState = AsyncAction.ConfirmingNoParams,
|
savingState = AsyncAction.ConfirmingNoParams,
|
||||||
eventSink = eventsRecorder,
|
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_3
|
||||||
import io.element.android.libraries.matrix.test.A_USER_ID_4
|
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_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 io.element.android.libraries.matrix.test.room.aRoomMember
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
@ -22,22 +24,28 @@ class MembersByRoleTest {
|
||||||
@Test
|
@Test
|
||||||
fun `constructor - with single member list categorizes and sorts members`() {
|
fun `constructor - with single member list categorizes and sorts members`() {
|
||||||
val members = listOf(
|
val members = listOf(
|
||||||
aRoomMember(A_USER_ID_2, displayName = "Bob", 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, displayName = "Alice", role = RoomMember.Role.Admin),
|
||||||
aRoomMember(A_USER_ID_3, displayName = "Carol", role = RoomMember.Role.USER),
|
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_5, displayName = "Eve", role = RoomMember.Role.User),
|
||||||
aRoomMember(A_USER_ID_4, displayName = "David", 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)
|
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(
|
assertThat(membersByRole.admins).containsExactly(
|
||||||
aRoomMember(A_USER_ID, displayName = "Alice", 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),
|
aRoomMember(A_USER_ID_2, displayName = "Bob", role = RoomMember.Role.Admin),
|
||||||
)
|
)
|
||||||
assertThat(membersByRole.moderators).isEmpty()
|
assertThat(membersByRole.moderators).isEmpty()
|
||||||
assertThat(membersByRole.members).containsExactly(
|
assertThat(membersByRole.members).containsExactly(
|
||||||
aRoomMember(A_USER_ID_3, displayName = "Carol", 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_4, displayName = "David", role = RoomMember.Role.User),
|
||||||
aRoomMember(A_USER_ID_5, displayName = "Eve", 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())
|
val emptyMembersByRole = MembersByRole(emptyList())
|
||||||
assertThat(emptyMembersByRole.isEmpty()).isTrue()
|
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(
|
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(),
|
moderators = persistentListOf(),
|
||||||
members = persistentListOf(),
|
members = persistentListOf(),
|
||||||
)
|
)
|
||||||
assertThat(membersByRoleWithAdmins.isEmpty()).isFalse()
|
assertThat(membersByRoleWithAdmins.isEmpty()).isFalse()
|
||||||
|
|
||||||
val membersByRoleWithModerators = MembersByRole(
|
val membersByRoleWithModerators = MembersByRole(
|
||||||
|
owners = persistentListOf(),
|
||||||
admins = 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(),
|
members = persistentListOf(),
|
||||||
)
|
)
|
||||||
assertThat(membersByRoleWithModerators.isEmpty()).isFalse()
|
assertThat(membersByRoleWithModerators.isEmpty()).isFalse()
|
||||||
|
|
||||||
val membersByRoleWithMembers = MembersByRole(
|
val membersByRoleWithMembers = MembersByRole(
|
||||||
|
owners = persistentListOf(),
|
||||||
admins = persistentListOf(),
|
admins = persistentListOf(),
|
||||||
moderators = 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()
|
assertThat(membersByRoleWithMembers.isEmpty()).isFalse()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,9 @@ import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import im.vector.app.features.analytics.plan.RoomModeration
|
import im.vector.app.features.analytics.plan.RoomModeration
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
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.Admin
|
||||||
import io.element.android.libraries.matrix.api.room.RoomMember.Role.MODERATOR
|
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.User
|
||||||
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
|
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.FakeBaseRoom
|
||||||
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
|
||||||
|
|
@ -100,13 +100,13 @@ class ChangeBaseRoomPermissionsPresenterTest {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val state = awaitUpdatedItem()
|
val state = awaitUpdatedItem()
|
||||||
assertThat(state.currentPermissions?.roomName).isEqualTo(ADMIN.powerLevel)
|
assertThat(state.currentPermissions?.roomName).isEqualTo(Admin.powerLevel)
|
||||||
assertThat(state.hasChanges).isFalse()
|
assertThat(state.hasChanges).isFalse()
|
||||||
|
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, MODERATOR))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator))
|
||||||
|
|
||||||
awaitItem().run {
|
awaitItem().run {
|
||||||
assertThat(currentPermissions?.roomName).isEqualTo(MODERATOR.powerLevel)
|
assertThat(currentPermissions?.roomName).isEqualTo(Moderator.powerLevel)
|
||||||
assertThat(hasChanges).isTrue()
|
assertThat(hasChanges).isTrue()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -120,28 +120,28 @@ class ChangeBaseRoomPermissionsPresenterTest {
|
||||||
}.test {
|
}.test {
|
||||||
val state = awaitUpdatedItem()
|
val state = awaitUpdatedItem()
|
||||||
|
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, MODERATOR))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, Moderator))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, MODERATOR))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, Moderator))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, MODERATOR))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, Moderator))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, MODERATOR))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, Moderator))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, MODERATOR))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, Moderator))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, MODERATOR))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, MODERATOR))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, Moderator))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, MODERATOR))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, Moderator))
|
||||||
|
|
||||||
val items = cancelAndConsumeRemainingEvents()
|
val items = cancelAndConsumeRemainingEvents()
|
||||||
|
|
||||||
(items.last() as? Event.Item<ChangeRoomPermissionsState>)?.value?.run {
|
(items.last() as? Event.Item<ChangeRoomPermissionsState>)?.value?.run {
|
||||||
assertThat(currentPermissions).isEqualTo(
|
assertThat(currentPermissions).isEqualTo(
|
||||||
RoomPowerLevelsValues(
|
RoomPowerLevelsValues(
|
||||||
invite = MODERATOR.powerLevel,
|
invite = Moderator.powerLevel,
|
||||||
kick = MODERATOR.powerLevel,
|
kick = Moderator.powerLevel,
|
||||||
ban = MODERATOR.powerLevel,
|
ban = Moderator.powerLevel,
|
||||||
redactEvents = MODERATOR.powerLevel,
|
redactEvents = Moderator.powerLevel,
|
||||||
sendEvents = MODERATOR.powerLevel,
|
sendEvents = Moderator.powerLevel,
|
||||||
roomName = MODERATOR.powerLevel,
|
roomName = Moderator.powerLevel,
|
||||||
roomAvatar = MODERATOR.powerLevel,
|
roomAvatar = Moderator.powerLevel,
|
||||||
roomTopic = MODERATOR.powerLevel,
|
roomTopic = Moderator.powerLevel,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -162,17 +162,17 @@ class ChangeBaseRoomPermissionsPresenterTest {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val state = awaitUpdatedItem()
|
val state = awaitUpdatedItem()
|
||||||
assertThat(state.currentPermissions?.roomName).isEqualTo(ADMIN.powerLevel)
|
assertThat(state.currentPermissions?.roomName).isEqualTo(Admin.powerLevel)
|
||||||
assertThat(state.hasChanges).isFalse()
|
assertThat(state.hasChanges).isFalse()
|
||||||
|
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, MODERATOR))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, MODERATOR))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_AVATAR, Moderator))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, MODERATOR))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_TOPIC, Moderator))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, MODERATOR))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.SEND_EVENTS, Moderator))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, USER))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.REDACT_EVENTS, User))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, ADMIN))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.KICK, Admin))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, ADMIN))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.BAN, Admin))
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, ADMIN))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.INVITE, Admin))
|
||||||
skipItems(7)
|
skipItems(7)
|
||||||
assertThat(awaitItem().hasChanges).isTrue()
|
assertThat(awaitItem().hasChanges).isTrue()
|
||||||
|
|
||||||
|
|
@ -181,7 +181,7 @@ class ChangeBaseRoomPermissionsPresenterTest {
|
||||||
assertThat(awaitItem().saveAction).isEqualTo(AsyncAction.Loading)
|
assertThat(awaitItem().saveAction).isEqualTo(AsyncAction.Loading)
|
||||||
assertThat(awaitItem().hasChanges).isFalse()
|
assertThat(awaitItem().hasChanges).isFalse()
|
||||||
awaitItem().run {
|
awaitItem().run {
|
||||||
assertThat(currentPermissions?.roomName).isEqualTo(MODERATOR.powerLevel)
|
assertThat(currentPermissions?.roomName).isEqualTo(Moderator.powerLevel)
|
||||||
assertThat(saveAction).isEqualTo(AsyncAction.Success(Unit))
|
assertThat(saveAction).isEqualTo(AsyncAction.Success(Unit))
|
||||||
}
|
}
|
||||||
assertThat(analyticsService.capturedEvents).containsExactlyElementsIn(
|
assertThat(analyticsService.capturedEvents).containsExactlyElementsIn(
|
||||||
|
|
@ -227,17 +227,17 @@ class ChangeBaseRoomPermissionsPresenterTest {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val state = awaitUpdatedItem()
|
val state = awaitUpdatedItem()
|
||||||
assertThat(state.currentPermissions?.roomName).isEqualTo(ADMIN.powerLevel)
|
assertThat(state.currentPermissions?.roomName).isEqualTo(Admin.powerLevel)
|
||||||
assertThat(state.hasChanges).isFalse()
|
assertThat(state.hasChanges).isFalse()
|
||||||
|
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, MODERATOR))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator))
|
||||||
assertThat(awaitItem().hasChanges).isTrue()
|
assertThat(awaitItem().hasChanges).isTrue()
|
||||||
|
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.Save)
|
state.eventSink(ChangeRoomPermissionsEvent.Save)
|
||||||
|
|
||||||
assertThat(awaitItem().saveAction).isEqualTo(AsyncAction.Loading)
|
assertThat(awaitItem().saveAction).isEqualTo(AsyncAction.Loading)
|
||||||
awaitItem().run {
|
awaitItem().run {
|
||||||
assertThat(currentPermissions?.roomName).isEqualTo(MODERATOR.powerLevel)
|
assertThat(currentPermissions?.roomName).isEqualTo(Moderator.powerLevel)
|
||||||
// Couldn't save the changes, so they're still pending
|
// Couldn't save the changes, so they're still pending
|
||||||
assertThat(hasChanges).isTrue()
|
assertThat(hasChanges).isTrue()
|
||||||
assertThat(saveAction).isInstanceOf(AsyncAction.Failure::class.java)
|
assertThat(saveAction).isInstanceOf(AsyncAction.Failure::class.java)
|
||||||
|
|
@ -245,7 +245,7 @@ class ChangeBaseRoomPermissionsPresenterTest {
|
||||||
|
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ResetPendingActions)
|
state.eventSink(ChangeRoomPermissionsEvent.ResetPendingActions)
|
||||||
awaitItem().run {
|
awaitItem().run {
|
||||||
assertThat(currentPermissions?.roomName).isEqualTo(MODERATOR.powerLevel)
|
assertThat(currentPermissions?.roomName).isEqualTo(Moderator.powerLevel)
|
||||||
assertThat(saveAction).isEqualTo(AsyncAction.Uninitialized)
|
assertThat(saveAction).isEqualTo(AsyncAction.Uninitialized)
|
||||||
assertThat(hasChanges).isTrue()
|
assertThat(hasChanges).isTrue()
|
||||||
}
|
}
|
||||||
|
|
@ -259,7 +259,7 @@ class ChangeBaseRoomPermissionsPresenterTest {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
val state = awaitUpdatedItem()
|
val state = awaitUpdatedItem()
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, MODERATOR))
|
state.eventSink(ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, Moderator))
|
||||||
assertThat(awaitItem().hasChanges).isTrue()
|
assertThat(awaitItem().hasChanges).isTrue()
|
||||||
|
|
||||||
state.eventSink(ChangeRoomPermissionsEvent.Exit)
|
state.eventSink(ChangeRoomPermissionsEvent.Exit)
|
||||||
|
|
|
||||||
|
|
@ -115,9 +115,9 @@ class ChangeBaseRoomPermissionsViewTest {
|
||||||
rule.onAllNodesWithText(users).onFirst().performClick()
|
rule.onAllNodesWithText(users).onFirst().performClick()
|
||||||
recorder.assertList(
|
recorder.assertList(
|
||||||
listOf(
|
listOf(
|
||||||
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.ADMIN),
|
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.Admin),
|
||||||
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.MODERATOR),
|
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.Moderator),
|
||||||
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.USER),
|
ChangeRoomPermissionsEvent.ChangeMinimumRoleForAction(RoomPermissionType.ROOM_NAME, RoomMember.Role.User),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,9 @@ data class RoomDescription(
|
||||||
enum class JoinRule {
|
enum class JoinRule {
|
||||||
PUBLIC,
|
PUBLIC,
|
||||||
KNOCK,
|
KNOCK,
|
||||||
|
RESTRICTED,
|
||||||
|
KNOCK_RESTRICTED,
|
||||||
|
INVITE,
|
||||||
UNKNOWN
|
UNKNOWN
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,9 @@ fun MatrixRoomDescription.toFeatureModel(): RoomDescription {
|
||||||
joinRule = when (joinRule) {
|
joinRule = when (joinRule) {
|
||||||
MatrixRoomDescription.JoinRule.PUBLIC -> RoomDescription.JoinRule.PUBLIC
|
MatrixRoomDescription.JoinRule.PUBLIC -> RoomDescription.JoinRule.PUBLIC
|
||||||
MatrixRoomDescription.JoinRule.KNOCK -> RoomDescription.JoinRule.KNOCK
|
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
|
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">"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_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_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>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -61,8 +61,8 @@ class RoomMemberModerationPresenterTest {
|
||||||
val room = aJoinedRoom(
|
val room = aJoinedRoom(
|
||||||
canBan = false,
|
canBan = false,
|
||||||
canKick = false,
|
canKick = false,
|
||||||
myUserRole = RoomMember.Role.USER,
|
myUserRole = RoomMember.Role.User,
|
||||||
targetRoomMember = aRoomMember(userId = A_USER_ID, powerLevel = RoomMember.Role.USER.powerLevel)
|
targetRoomMember = aRoomMember(userId = A_USER_ID, powerLevel = RoomMember.Role.User.powerLevel)
|
||||||
)
|
)
|
||||||
createRoomMemberModerationPresenter(room = room).test {
|
createRoomMemberModerationPresenter(room = room).test {
|
||||||
val initialState = awaitState()
|
val initialState = awaitState()
|
||||||
|
|
@ -81,7 +81,7 @@ class RoomMemberModerationPresenterTest {
|
||||||
val room = aJoinedRoom(
|
val room = aJoinedRoom(
|
||||||
canBan = true,
|
canBan = true,
|
||||||
canKick = true,
|
canKick = true,
|
||||||
myUserRole = RoomMember.Role.ADMIN,
|
myUserRole = RoomMember.Role.Admin,
|
||||||
targetRoomMember = null
|
targetRoomMember = null
|
||||||
)
|
)
|
||||||
createRoomMemberModerationPresenter(room = room).test {
|
createRoomMemberModerationPresenter(room = room).test {
|
||||||
|
|
@ -103,8 +103,8 @@ class RoomMemberModerationPresenterTest {
|
||||||
val room = aJoinedRoom(
|
val room = aJoinedRoom(
|
||||||
canBan = true,
|
canBan = true,
|
||||||
canKick = true,
|
canKick = true,
|
||||||
myUserRole = RoomMember.Role.ADMIN,
|
myUserRole = RoomMember.Role.Admin,
|
||||||
targetRoomMember = aRoomMember(userId = A_USER_ID, powerLevel = RoomMember.Role.USER.powerLevel)
|
targetRoomMember = aRoomMember(userId = A_USER_ID, powerLevel = RoomMember.Role.User.powerLevel)
|
||||||
)
|
)
|
||||||
createRoomMemberModerationPresenter(room = room).test {
|
createRoomMemberModerationPresenter(room = room).test {
|
||||||
val initialState = awaitState()
|
val initialState = awaitState()
|
||||||
|
|
@ -125,8 +125,8 @@ class RoomMemberModerationPresenterTest {
|
||||||
val room = aJoinedRoom(
|
val room = aJoinedRoom(
|
||||||
canBan = true,
|
canBan = true,
|
||||||
canKick = true,
|
canKick = true,
|
||||||
myUserRole = RoomMember.Role.MODERATOR,
|
myUserRole = RoomMember.Role.Moderator,
|
||||||
targetRoomMember = aRoomMember(userId = A_USER_ID, powerLevel = RoomMember.Role.ADMIN.powerLevel)
|
targetRoomMember = aRoomMember(userId = A_USER_ID, powerLevel = RoomMember.Role.Admin.powerLevel)
|
||||||
)
|
)
|
||||||
createRoomMemberModerationPresenter(room = room).test {
|
createRoomMemberModerationPresenter(room = room).test {
|
||||||
val initialState = awaitState()
|
val initialState = awaitState()
|
||||||
|
|
@ -147,7 +147,7 @@ class RoomMemberModerationPresenterTest {
|
||||||
val room = aJoinedRoom(
|
val room = aJoinedRoom(
|
||||||
canBan = true,
|
canBan = true,
|
||||||
canKick = true,
|
canKick = true,
|
||||||
myUserRole = RoomMember.Role.MODERATOR,
|
myUserRole = RoomMember.Role.Moderator,
|
||||||
targetRoomMember = aRoomMember(userId = A_USER_ID, membership = RoomMembershipState.BAN)
|
targetRoomMember = aRoomMember(userId = A_USER_ID, membership = RoomMembershipState.BAN)
|
||||||
)
|
)
|
||||||
createRoomMemberModerationPresenter(room = room).test {
|
createRoomMemberModerationPresenter(room = room).test {
|
||||||
|
|
@ -321,7 +321,7 @@ class RoomMemberModerationPresenterTest {
|
||||||
private fun aJoinedRoom(
|
private fun aJoinedRoom(
|
||||||
canKick: Boolean = false,
|
canKick: Boolean = false,
|
||||||
canBan: Boolean = false,
|
canBan: Boolean = false,
|
||||||
myUserRole: RoomMember.Role = RoomMember.Role.USER,
|
myUserRole: RoomMember.Role = RoomMember.Role.User,
|
||||||
kickUserResult: Result<Unit> = Result.success(Unit),
|
kickUserResult: Result<Unit> = Result.success(Unit),
|
||||||
banUserResult: Result<Unit> = Result.success(Unit),
|
banUserResult: Result<Unit> = Result.success(Unit),
|
||||||
unBanUserResult: Result<Unit> = Result.success(Unit),
|
unBanUserResult: Result<Unit> = Result.success(Unit),
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,7 @@ class SecureBackupSetupPresenter @AssistedInject constructor(
|
||||||
stateAndDispatch.dispatchAction(SecureBackupSetupStateMachine.Event.UserSavedKey)
|
stateAndDispatch.dispatchAction(SecureBackupSetupStateMachine.Event.UserSavedKey)
|
||||||
SecureBackupSetupEvents.DismissDialog -> {
|
SecureBackupSetupEvents.DismissDialog -> {
|
||||||
showSaveConfirmationDialog = false
|
showSaveConfirmationDialog = false
|
||||||
|
stateAndDispatch.dispatchAction(SecureBackupSetupStateMachine.Event.ClearError)
|
||||||
}
|
}
|
||||||
SecureBackupSetupEvents.Done -> {
|
SecureBackupSetupEvents.Done -> {
|
||||||
showSaveConfirmationDialog = true
|
showSaveConfirmationDialog = true
|
||||||
|
|
@ -89,6 +90,7 @@ class SecureBackupSetupPresenter @AssistedInject constructor(
|
||||||
SecureBackupSetupStateMachine.State.CreatingKey -> SetupState.Creating
|
SecureBackupSetupStateMachine.State.CreatingKey -> SetupState.Creating
|
||||||
is SecureBackupSetupStateMachine.State.KeyCreated -> SetupState.Created(formattedRecoveryKey = key)
|
is SecureBackupSetupStateMachine.State.KeyCreated -> SetupState.Created(formattedRecoveryKey = key)
|
||||||
is SecureBackupSetupStateMachine.State.KeyCreatedAndSaved -> SetupState.CreatedAndSaved(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))
|
stateAndDispatch.dispatchAction(SecureBackupSetupStateMachine.Event.SdkHasCreatedKey(it))
|
||||||
},
|
},
|
||||||
onFailure = {
|
onFailure = {
|
||||||
stateAndDispatch.dispatchAction(SecureBackupSetupStateMachine.Event.SdkError(it))
|
if (it is Exception) {
|
||||||
|
stateAndDispatch.dispatchAction(SecureBackupSetupStateMachine.Event.SdkError(it))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
observeEncryptionService(stateAndDispatch)
|
observeEncryptionService(stateAndDispatch)
|
||||||
Timber.tag(loggerTagSetup.value).d("Calling encryptionService.enableRecovery()")
|
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 object Creating : SetupState
|
||||||
data class Created(val formattedRecoveryKey: String) : SetupState
|
data class Created(val formattedRecoveryKey: String) : SetupState
|
||||||
data class CreatedAndSaved(val formattedRecoveryKey: String) : SetupState
|
data class CreatedAndSaved(val formattedRecoveryKey: String) : SetupState
|
||||||
|
data class Error(val exception: Exception) : SetupState
|
||||||
}
|
}
|
||||||
|
|
||||||
fun SetupState.recoveryKey(): String? = when (this) {
|
fun SetupState.recoveryKey(): String? = when (this) {
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,8 @@ class SecureBackupSetupStateMachine @Inject constructor() : FlowReduxStateMachin
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inState<State.CreatingKey> {
|
inState<State.CreatingKey> {
|
||||||
on { _: Event.SdkError, state: MachineState<State.CreatingKey> ->
|
on { event: Event.SdkError, state: MachineState<State.CreatingKey> ->
|
||||||
state.override { State.Initial }
|
state.override { State.Error(event.exception) }
|
||||||
}
|
}
|
||||||
on { event: Event.SdkHasCreatedKey, state: MachineState<State.CreatingKey> ->
|
on { event: Event.SdkHasCreatedKey, state: MachineState<State.CreatingKey> ->
|
||||||
state.override { State.KeyCreated(event.key) }
|
state.override { State.KeyCreated(event.key) }
|
||||||
|
|
@ -38,6 +38,11 @@ class SecureBackupSetupStateMachine @Inject constructor() : FlowReduxStateMachin
|
||||||
state.override { State.KeyCreatedAndSaved(state.snapshot.key) }
|
state.override { State.KeyCreatedAndSaved(state.snapshot.key) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
inState<State.Error> {
|
||||||
|
on { _: Event.ClearError, state: MachineState<State.Error> ->
|
||||||
|
state.override { State.Initial }
|
||||||
|
}
|
||||||
|
}
|
||||||
inState<State.KeyCreatedAndSaved> {
|
inState<State.KeyCreatedAndSaved> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -48,12 +53,14 @@ class SecureBackupSetupStateMachine @Inject constructor() : FlowReduxStateMachin
|
||||||
data object CreatingKey : State
|
data object CreatingKey : State
|
||||||
data class KeyCreated(val key: String) : State
|
data class KeyCreated(val key: String) : State
|
||||||
data class KeyCreatedAndSaved(val key: String) : State
|
data class KeyCreatedAndSaved(val key: String) : State
|
||||||
|
data class Error(val exception: Exception) : State
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface Event {
|
sealed interface Event {
|
||||||
data object UserCreatesKey : Event
|
data object UserCreatesKey : Event
|
||||||
data class SdkHasCreatedKey(val key: String) : 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 UserSavedKey : Event
|
||||||
|
data object ClearError : Event
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ open class SecureBackupSetupStateProvider : PreviewParameterProvider<SecureBacku
|
||||||
setupState = SetupState.CreatedAndSaved(aFormattedRecoveryKey()),
|
setupState = SetupState.CreatedAndSaved(aFormattedRecoveryKey()),
|
||||||
showSaveConfirmationDialog = true,
|
showSaveConfirmationDialog = true,
|
||||||
),
|
),
|
||||||
|
aSecureBackupSetupState(setupState = SetupState.Error(Exception("Test error"))),
|
||||||
// Add other states here
|
// 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