Open user profile and room with event from permalink
This commit is contained in:
committed by
Benoit Marty
parent
00e5ad5078
commit
66cfaf97c9
@@ -74,6 +74,35 @@
|
||||
|
||||
<data android:scheme="io.element" />
|
||||
</intent-filter>
|
||||
<!--
|
||||
Element web links
|
||||
Note: On Android 12 and higher clicking a web link (that is not an Android App Link) always shows content in a web browser
|
||||
https://developer.android.com/training/app-links#web-links
|
||||
-->
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:host="app.element.io" />
|
||||
<data android:host="develop.element.io" />
|
||||
</intent-filter>
|
||||
<!--
|
||||
matrix.to links
|
||||
Note: On Android 12 and higher clicking a web link (that is not an Android App Link) always shows content in a web browser
|
||||
https://developer.android.com/training/app-links#web-links
|
||||
-->
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="https" />
|
||||
<data android:host="matrix.to" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
|
||||
@@ -56,6 +56,7 @@ import io.element.android.features.roomdirectory.api.RoomDescription
|
||||
import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint
|
||||
import io.element.android.features.roomlist.api.RoomListEntryPoint
|
||||
import io.element.android.features.securebackup.api.SecureBackupEntryPoint
|
||||
import io.element.android.features.userprofile.api.UserProfileEntryPoint
|
||||
import io.element.android.libraries.architecture.BackstackView
|
||||
import io.element.android.libraries.architecture.BaseFlowNode
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
@@ -64,9 +65,11 @@ import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatch
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.MAIN_SPACE
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.api.sync.SyncState
|
||||
@@ -91,6 +94,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||
private val createRoomEntryPoint: CreateRoomEntryPoint,
|
||||
private val appNavigationStateService: AppNavigationStateService,
|
||||
private val secureBackupEntryPoint: SecureBackupEntryPoint,
|
||||
private val userProfileEntryPoint: UserProfileEntryPoint,
|
||||
private val ftueEntryPoint: FtueEntryPoint,
|
||||
private val coroutineScope: CoroutineScope,
|
||||
private val networkMonitor: NetworkMonitor,
|
||||
@@ -197,6 +201,11 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||
val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages()
|
||||
) : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data class UserProfile(
|
||||
val userId: UserId,
|
||||
) : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data class Settings(
|
||||
val initialElement: PreferencesEntryPoint.InitialTarget = PreferencesEntryPoint.InitialTarget.Root
|
||||
@@ -270,14 +279,14 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||
}
|
||||
|
||||
override fun onForwardedToSingleRoom(roomId: RoomId) {
|
||||
coroutineScope.launch { attachRoom(roomId) }
|
||||
coroutineScope.launch { attachRoom(roomId.toRoomIdOrAlias()) }
|
||||
}
|
||||
|
||||
override fun onPermalinkClicked(data: PermalinkData) {
|
||||
when (data) {
|
||||
is PermalinkData.UserLink -> {
|
||||
// FIXME Add a user profile screen.
|
||||
Timber.e("User link clicked: ${data.userId}. TODO Add a user profile screen")
|
||||
// Should not happen (handled by MessagesNode)
|
||||
Timber.e("User link clicked: ${data.userId}.")
|
||||
}
|
||||
is PermalinkData.RoomLink -> {
|
||||
backstack.push(
|
||||
@@ -306,6 +315,17 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||
)
|
||||
createNode<RoomFlowNode>(buildContext, plugins = listOf(inputs, callback))
|
||||
}
|
||||
is NavTarget.UserProfile -> {
|
||||
val callback = object : UserProfileEntryPoint.Callback {
|
||||
override fun onOpenRoom(roomId: RoomId) {
|
||||
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias()))
|
||||
}
|
||||
}
|
||||
userProfileEntryPoint.nodeBuilder(this, buildContext)
|
||||
.params(UserProfileEntryPoint.Params(userId = navTarget.userId))
|
||||
.callback(callback)
|
||||
.build()
|
||||
}
|
||||
is NavTarget.Settings -> {
|
||||
val callback = object : PreferencesEntryPoint.Callback {
|
||||
override fun onOpenBugReport() {
|
||||
@@ -321,7 +341,7 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
val inputs = PreferencesEntryPoint.Params(navTarget.initialElement)
|
||||
return preferencesEntryPoint.nodeBuilder(this, buildContext)
|
||||
preferencesEntryPoint.nodeBuilder(this, buildContext)
|
||||
.params(inputs)
|
||||
.callback(callback)
|
||||
.build()
|
||||
@@ -363,12 +383,32 @@ class LoggedInFlowNode @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun attachRoom(roomId: RoomId) {
|
||||
suspend fun attachRoom(roomIdOrAlias: RoomIdOrAlias, eventId: EventId? = null) {
|
||||
waitForNavTargetAttached { navTarget ->
|
||||
navTarget is NavTarget.RoomList
|
||||
}
|
||||
attachChild<RoomFlowNode> {
|
||||
backstack.push(NavTarget.Room(roomId.toRoomIdOrAlias()))
|
||||
backstack.push(
|
||||
NavTarget.Room(
|
||||
roomIdOrAlias = roomIdOrAlias,
|
||||
initialElement = RoomNavigationTarget.Messages(
|
||||
focusedEventId = eventId
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun attachUser(userId: UserId) {
|
||||
waitForNavTargetAttached { navTarget ->
|
||||
navTarget is NavTarget.RoomList
|
||||
}
|
||||
attachChild<Node> {
|
||||
backstack.push(
|
||||
NavTarget.UserProfile(
|
||||
userId = userId,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +55,8 @@ import io.element.android.libraries.designsystem.theme.components.CircularProgre
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.libraries.sessionstorage.api.LoggedInState
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
@@ -279,9 +281,31 @@ class RootFlowNode @AssistedInject constructor(
|
||||
when (resolvedIntent) {
|
||||
is ResolvedIntent.Navigation -> navigateTo(resolvedIntent.deeplinkData)
|
||||
is ResolvedIntent.Oidc -> onOidcAction(resolvedIntent.oidcAction)
|
||||
is ResolvedIntent.Permalink -> navigateTo(resolvedIntent.permalinkData)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun navigateTo(permalinkData: PermalinkData) {
|
||||
Timber.d("Navigating to $permalinkData")
|
||||
attachSession(null)
|
||||
.attachSession()
|
||||
.apply {
|
||||
when (permalinkData) {
|
||||
is PermalinkData.FallbackLink -> Unit
|
||||
is PermalinkData.RoomEmailInviteLink -> Unit
|
||||
is PermalinkData.RoomLink -> {
|
||||
attachRoom(
|
||||
roomIdOrAlias = permalinkData.roomIdOrAlias,
|
||||
eventId = permalinkData.eventId,
|
||||
)
|
||||
}
|
||||
is PermalinkData.UserLink -> {
|
||||
attachUser(permalinkData.userId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun navigateTo(deeplinkData: DeeplinkData) {
|
||||
Timber.d("Navigating to $deeplinkData")
|
||||
attachSession(deeplinkData.sessionId)
|
||||
@@ -289,7 +313,7 @@ class RootFlowNode @AssistedInject constructor(
|
||||
.apply {
|
||||
when (deeplinkData) {
|
||||
is DeeplinkData.Root -> Unit // The room list will always be shown, observing FtueState
|
||||
is DeeplinkData.Room -> attachRoom(deeplinkData.roomId)
|
||||
is DeeplinkData.Room -> attachRoom(deeplinkData.roomId.toRoomIdOrAlias())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -298,10 +322,11 @@ class RootFlowNode @AssistedInject constructor(
|
||||
oidcActionFlow.post(oidcAction)
|
||||
}
|
||||
|
||||
private suspend fun attachSession(sessionId: SessionId): LoggedInAppScopeFlowNode {
|
||||
// [sessionId] will be null for permalink.
|
||||
private suspend fun attachSession(sessionId: SessionId?): LoggedInAppScopeFlowNode {
|
||||
// TODO handle multi-session
|
||||
return waitForChildAttached { navTarget ->
|
||||
navTarget is NavTarget.LoggedInFlow && navTarget.sessionId == sessionId
|
||||
navTarget is NavTarget.LoggedInFlow && (sessionId == null || navTarget.sessionId == sessionId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,27 +21,41 @@ import io.element.android.features.login.api.oidc.OidcAction
|
||||
import io.element.android.features.login.api.oidc.OidcIntentResolver
|
||||
import io.element.android.libraries.deeplink.DeeplinkData
|
||||
import io.element.android.libraries.deeplink.DeeplinkParser
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
sealed interface ResolvedIntent {
|
||||
data class Navigation(val deeplinkData: DeeplinkData) : ResolvedIntent
|
||||
data class Oidc(val oidcAction: OidcAction) : ResolvedIntent
|
||||
data class Permalink(val permalinkData: PermalinkData) : ResolvedIntent
|
||||
}
|
||||
|
||||
class IntentResolver @Inject constructor(
|
||||
private val deeplinkParser: DeeplinkParser,
|
||||
private val oidcIntentResolver: OidcIntentResolver
|
||||
private val oidcIntentResolver: OidcIntentResolver,
|
||||
private val permalinkParser: PermalinkParser,
|
||||
) {
|
||||
fun resolve(intent: Intent): ResolvedIntent? {
|
||||
if (intent.canBeIgnored()) return null
|
||||
|
||||
// Coming from a notification?
|
||||
val deepLinkData = deeplinkParser.getFromIntent(intent)
|
||||
if (deepLinkData != null) return ResolvedIntent.Navigation(deepLinkData)
|
||||
|
||||
// Coming during login using Oidc?
|
||||
val oidcAction = oidcIntentResolver.resolve(intent)
|
||||
if (oidcAction != null) return ResolvedIntent.Oidc(oidcAction)
|
||||
|
||||
// External link clicked? (matrix.to, element.io, etc.)
|
||||
val permalinkData = intent
|
||||
.takeIf { it.action == Intent.ACTION_VIEW }
|
||||
?.dataString
|
||||
?.let { permalinkParser.parse(it) }
|
||||
?.takeIf { it !is PermalinkData.FallbackLink }
|
||||
if (permalinkData != null) return ResolvedIntent.Permalink(permalinkData)
|
||||
|
||||
// Unknown intent
|
||||
Timber.w("Unknown intent")
|
||||
return null
|
||||
|
||||
@@ -18,6 +18,7 @@ package io.element.android.appnav.intent
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.core.net.toUri
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.login.api.oidc.OidcAction
|
||||
@@ -26,9 +27,11 @@ import io.element.android.features.login.impl.oidc.OidcUrlParser
|
||||
import io.element.android.libraries.deeplink.DeepLinkCreator
|
||||
import io.element.android.libraries.deeplink.DeeplinkData
|
||||
import io.element.android.libraries.deeplink.DeeplinkParser
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.A_THREAD_ID
|
||||
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@@ -179,6 +182,9 @@ class IntentResolverTest {
|
||||
oidcIntentResolver = DefaultOidcIntentResolver(
|
||||
oidcUrlParser = OidcUrlParser()
|
||||
),
|
||||
permalinkParser = FakePermalinkParser(
|
||||
result = { PermalinkData.FallbackLink(Uri.parse("https://matrix.org")) }
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<string name="screen_create_room_private_option_title">"Privater Raum (nur auf Einladung)"</string>
|
||||
<string name="screen_create_room_public_option_description">"Die Nachrichten sind nicht verschlüsselt und können von jedem gelesen werden. Die Verschlüsselung kann zu einem späteren Zeitpunkt aktiviert werden."</string>
|
||||
<string name="screen_create_room_public_option_title">"Öffentlicher Raum (für alle)"</string>
|
||||
<string name="screen_create_room_room_name_label">"Raum-Name"</string>
|
||||
<string name="screen_create_room_room_name_label">"Raumname"</string>
|
||||
<string name="screen_create_room_title">"Raum erstellen"</string>
|
||||
<string name="screen_create_room_topic_label">"Thema (optional)"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Beim Versuch, einen Chat zu starten, ist ein Fehler aufgetreten"</string>
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
<string name="screen_qr_code_login_initial_state_item_2">"Націсніце на свой аватар"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3">"Выберыце %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3_action">"“Звязаць новую прыладу”"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Выберыце %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4_action">"“Паказаць QR-код”"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Выконвайце паказаныя інструкцыі"</string>
|
||||
<string name="screen_qr_code_login_initial_state_title">"Адкрыйце %1$s на іншай прыладзе, каб атрымаць QR-код"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Выкарыстоўвайце QR-код, паказаны на іншай прыладзе."</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Паўтарыць спробу"</string>
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
<string name="screen_qr_code_login_initial_state_item_2">"Klikněte na svůj avatar"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3">"Vybrat %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3_action">"\"Připojit nové zařízení\""</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Vybrat %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4_action">"\"Zobrazit QR kód\""</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Postupujte podle uvedených pokynů"</string>
|
||||
<string name="screen_qr_code_login_initial_state_title">"Otevřete %1$s na jiném zařízení pro získání QR kódu"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Použijte QR kód zobrazený na druhém zařízení."</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Zkusit znovu"</string>
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
<string name="screen_qr_code_login_initial_state_item_2">"Klick auf deinen Avatar"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3">"Wähle %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3_action">"\"Neues Gerät verknüpfen\""</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Wähle %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4_action">"\"QR-Code anzeigen\""</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Befolge die angezeigten Anweisungen"</string>
|
||||
<string name="screen_qr_code_login_initial_state_title">"Öffne %1$s auf einem anderen Gerät, um den QR-Code zu erhalten"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Verwende den QR-Code, der auf dem anderen Gerät angezeigt wird."</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Erneut versuchen"</string>
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
<string name="screen_qr_code_login_initial_state_item_2">"Cliquez sur votre image de profil"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3">"Choisissez %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3_action">"“Associer une nouvelle session”"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Choisissez %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4_action">"“Afficher le QR code”"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Suivez les instructions affichées"</string>
|
||||
<string name="screen_qr_code_login_initial_state_title">"Ouvrez %1$s sur un autre appareil pour obtenir le QR code"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Scannez le QR code affiché sur l’autre appareil."</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Essayer à nouveau"</string>
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
<string name="screen_qr_code_login_initial_state_item_2">"Kattintson a profilképére"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3">"Válassza ezt: %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3_action">"„Új eszköz összekapcsolása”"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Válassza ezt: %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4_action">"„QR-kód megjelenítése”"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Kövesse a látható utasításokat"</string>
|
||||
<string name="screen_qr_code_login_initial_state_title">"Nyissa meg az %1$set egy másik eszközön a QR-kód lekéréséhez."</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Használja a másik eszközön látható QR-kódot."</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Próbálja újra"</string>
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
<string name="screen_qr_code_login_initial_state_item_2">"Klik pada avatar Anda"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3">"Pilih %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3_action">"“Tautkan perangkat baru”"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Pilih %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4_action">"“Tampilkan kode QR”"</string>
|
||||
<string name="screen_qr_code_login_initial_state_title">"Buka %1$s di perangkat lain untuk mendapatkan kode QR"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Gunakan kode QR yang ditampilkan di perangkat lain."</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Coba lagi"</string>
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
<string name="screen_qr_code_login_initial_state_item_2">"Нажмите на свое изображение"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3">"Выбрать %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3_action">"\"Привязать новое устройство\""</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Выбрать %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4_action">"\"Показать QR-код\""</string>
|
||||
<string name="screen_qr_code_login_initial_state_title">"Откройте %1$s на другом устройстве, чтобы получить QR-код"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Используйте QR-код, показанный на другом устройстве."</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Повторить попытку"</string>
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
<string name="screen_qr_code_login_initial_state_item_2">"Kliknite na svoj obrázok"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3">"Vyberte %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3_action">"„Prepojiť nové zariadenie“"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Vyberte %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4_action">"„Zobraziť QR kód“"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Postupujte podľa zobrazených pokynov"</string>
|
||||
<string name="screen_qr_code_login_initial_state_title">"Ak chcete získať QR kód, otvorte %1$s na inom zariadení"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Použite QR kód zobrazený na druhom zariadení."</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Skúste to znova"</string>
|
||||
|
||||
@@ -15,8 +15,7 @@
|
||||
<string name="screen_qr_code_login_initial_state_item_2">"Click on your avatar"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3">"Select %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_3_action">"“Link new device”"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Select %1$s"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4_action">"“Show QR code”"</string>
|
||||
<string name="screen_qr_code_login_initial_state_item_4">"Follow the instructions shown"</string>
|
||||
<string name="screen_qr_code_login_initial_state_title">"Open %1$s on another device to get the QR code"</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_description">"Use the QR code shown on the other device."</string>
|
||||
<string name="screen_qr_code_login_invalid_scan_state_retry_button">"Try again"</string>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<string name="screen_app_lock_setup_confirm_pin">"PIN bestätigen"</string>
|
||||
<string name="screen_app_lock_setup_pin_blacklisted_dialog_content">"Aus Sicherheitsgründen kann dieser PIN-Code nicht verwendet werden."</string>
|
||||
<string name="screen_app_lock_setup_pin_blacklisted_dialog_title">"Bitte eine andere PIN verwenden."</string>
|
||||
<string name="screen_app_lock_setup_pin_context">"Sperre %1$s mit einem PIN Code, um den Zugriff auf Deine Chats zu beschränken.
|
||||
<string name="screen_app_lock_setup_pin_context">"Sperre %1$s mit einem PIN Code, um den Zugriff auf deine Chats zu beschränken.
|
||||
|
||||
Wähle etwas Einprägsames. Bei falscher Eingabe wirst du aus der App ausgeloggt."</string>
|
||||
<string name="screen_app_lock_setup_pin_mismatch_dialog_content">"Bitte gib die gleiche PIN wie zuvor ein."</string>
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_app_lock_biometric_authentication">"biometrisk autentisering"</string>
|
||||
<string name="screen_app_lock_biometric_unlock">"biometrisk upplåsning"</string>
|
||||
<string name="screen_app_lock_biometric_unlock_title_android">"Lås upp med biometri"</string>
|
||||
<string name="screen_app_lock_forgot_pin">"Glömt PIN-kod?"</string>
|
||||
<string name="screen_app_lock_settings_change_pin">"Byt PIN-kod"</string>
|
||||
<string name="screen_app_lock_settings_enable_biometric_unlock">"Tillåt biometrisk upplåsning"</string>
|
||||
<string name="screen_app_lock_settings_remove_pin">"Ta bort PIN-kod"</string>
|
||||
<string name="screen_app_lock_settings_remove_pin_alert_message">"Är du säker på att du vill ta bort PIN-koden?"</string>
|
||||
<string name="screen_app_lock_settings_remove_pin_alert_title">"Ta bort PIN-koden?"</string>
|
||||
<string name="screen_app_lock_setup_biometric_unlock_allow_title">"Tillåt %1$s"</string>
|
||||
<string name="screen_app_lock_setup_biometric_unlock_skip">"Jag vill hellre använda PIN-kod"</string>
|
||||
<string name="screen_app_lock_setup_biometric_unlock_subtitle">"Bespara dig själv lite tid och använd %1$s för att låsa upp appen varje gång"</string>
|
||||
<string name="screen_app_lock_setup_choose_pin">"Välj PIN-kod"</string>
|
||||
<string name="screen_app_lock_setup_confirm_pin">"Bekräfta PIN-kod"</string>
|
||||
<string name="screen_app_lock_setup_pin_blacklisted_dialog_content">"Du kan inte välja detta som din PIN-kod av säkerhetsskäl"</string>
|
||||
@@ -25,5 +31,7 @@ Välj något minnesvärt. Om du glömmer den här PIN-koden loggas du ut från a
|
||||
<item quantity="one">"Fel PIN-kod. Du har %1$d försök kvar"</item>
|
||||
<item quantity="other">"Fel PIN-kod. Du har %1$d försök kvar"</item>
|
||||
</plurals>
|
||||
<string name="screen_app_lock_use_biometric_android">"Använd biometri"</string>
|
||||
<string name="screen_app_lock_use_pin_android">"Använd PIN-kod"</string>
|
||||
<string name="screen_signout_in_progress_dialog_content">"Loggar ut …"</string>
|
||||
</resources>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<string name="screen_signout_confirmation_dialog_title">"Abmelden"</string>
|
||||
<string name="screen_signout_in_progress_dialog_content">"Abmelden…"</string>
|
||||
<string name="screen_signout_key_backup_disabled_subtitle">"Du bist dabei, dich von deiner letzten Sitzung abzumelden. Wenn du dich jetzt abmeldest, verlierst du den Zugriff auf deine verschlüsselten Nachrichten."</string>
|
||||
<string name="screen_signout_key_backup_disabled_title">"Du hast das Backup ausgeschaltet"</string>
|
||||
<string name="screen_signout_key_backup_disabled_title">"Du hast das Backup deaktiviert."</string>
|
||||
<string name="screen_signout_key_backup_offline_subtitle">"Deine Schlüssel wurden noch gesichert, als du offline gegangen bist. Stelle die Verbindung wieder her, damit deine Schlüssel gesichert werden können, bevor du dich abmeldest."</string>
|
||||
<string name="screen_signout_key_backup_offline_title">"Deine Schlüssel werden noch gesichert"</string>
|
||||
<string name="screen_signout_key_backup_ongoing_subtitle">"Bitte warte, bis der Vorgang abgeschlossen ist, bevor du dich abmeldest."</string>
|
||||
|
||||
@@ -4,8 +4,15 @@
|
||||
<string name="screen_signout_confirmation_dialog_submit">"Logga ut"</string>
|
||||
<string name="screen_signout_confirmation_dialog_title">"Logga ut"</string>
|
||||
<string name="screen_signout_in_progress_dialog_content">"Loggar ut …"</string>
|
||||
<string name="screen_signout_key_backup_disabled_subtitle">"Du är på väg att logga ut ur din senaste session. Om du loggar ut nu kommer du att förlora åtkomsten till dina krypterade meddelanden."</string>
|
||||
<string name="screen_signout_key_backup_disabled_title">"Du har stängt av säkerhetskopiering"</string>
|
||||
<string name="screen_signout_key_backup_offline_subtitle">"Dina nycklar säkerhetskopierades fortfarande när du gick offline. Anslut igen så att dina nycklar kan säkerhetskopieras innan du loggar ut."</string>
|
||||
<string name="screen_signout_key_backup_offline_title">"Dina nycklar säkerhetskopieras fortfarande"</string>
|
||||
<string name="screen_signout_key_backup_ongoing_subtitle">"Vänta tills detta är klart innan du loggar ut."</string>
|
||||
<string name="screen_signout_key_backup_ongoing_title">"Dina nycklar säkerhetskopieras fortfarande"</string>
|
||||
<string name="screen_signout_preference_item">"Logga ut"</string>
|
||||
<string name="screen_signout_recovery_disabled_subtitle">"Du är på väg att logga ut ur din sista session. Om du loggar ut nu förlorar du åtkomsten till dina krypterade meddelanden."</string>
|
||||
<string name="screen_signout_recovery_disabled_title">"Återställning inte inställd"</string>
|
||||
<string name="screen_signout_save_recovery_key_subtitle">"Du är på väg att logga ut från din senaste session. Om du loggar ut nu kan du förlora åtkomsten till dina krypterade meddelanden."</string>
|
||||
<string name="screen_signout_save_recovery_key_title">"Har du sparat din återställningsnyckel?"</string>
|
||||
</resources>
|
||||
|
||||
@@ -21,8 +21,10 @@
|
||||
<string name="screen_room_attachment_source_poll">"Omröstning"</string>
|
||||
<string name="screen_room_attachment_text_formatting">"Textformatering"</string>
|
||||
<string name="screen_room_encrypted_history_banner">"Meddelandehistoriken är för närvarande otillgänglig."</string>
|
||||
<string name="screen_room_encrypted_history_banner_unverified">"Meddelandehistorik är inte tillgänglig i det här rummet. Verifiera den här enheten för att se din meddelandehistorik."</string>
|
||||
<string name="screen_room_invite_again_alert_message">"Vill du bjuda tillbaka dem?"</string>
|
||||
<string name="screen_room_invite_again_alert_title">"Du är ensam i den här chatten"</string>
|
||||
<string name="screen_room_mentions_at_room_subtitle">"Meddela hela rummet"</string>
|
||||
<string name="screen_room_mentions_at_room_title">"Alla"</string>
|
||||
<string name="screen_room_retry_send_menu_send_again_action">"Skicka igen"</string>
|
||||
<string name="screen_room_retry_send_menu_title">"Ditt meddelande kunde inte skickas"</string>
|
||||
|
||||
@@ -58,6 +58,7 @@ dependencies {
|
||||
implementation(libs.coil.compose)
|
||||
implementation(projects.features.leaveroom.api)
|
||||
implementation(projects.features.createroom.api)
|
||||
implementation(projects.features.userprofile.shared)
|
||||
implementation(projects.services.analytics.api)
|
||||
implementation(projects.features.poll.api)
|
||||
|
||||
|
||||
@@ -34,9 +34,10 @@ import io.element.android.features.roomdetails.impl.edit.RoomDetailsEditNode
|
||||
import io.element.android.features.roomdetails.impl.invite.RoomInviteMembersNode
|
||||
import io.element.android.features.roomdetails.impl.members.RoomMemberListNode
|
||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsNode
|
||||
import io.element.android.features.roomdetails.impl.members.details.avatar.AvatarPreviewNode
|
||||
import io.element.android.features.roomdetails.impl.notificationsettings.RoomNotificationSettingsNode
|
||||
import io.element.android.features.roomdetails.impl.rolesandpermissions.RolesAndPermissionsFlowNode
|
||||
import io.element.android.features.userprofile.shared.UserProfileNodeHelper
|
||||
import io.element.android.features.userprofile.shared.avatar.AvatarPreviewNode
|
||||
import io.element.android.libraries.architecture.BackstackView
|
||||
import io.element.android.libraries.architecture.BaseFlowNode
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
@@ -78,7 +79,7 @@ class RoomDetailsFlowNode @AssistedInject constructor(
|
||||
@Parcelize
|
||||
data class RoomNotificationSettings(
|
||||
/**
|
||||
* When presented from outsite the context of the room, the rooms settings UI is different.
|
||||
* When presented from outside the context of the room, the rooms settings UI is different.
|
||||
* Figma designs: https://www.figma.com/file/0MMNu7cTOzLOlWb7ctTkv3/Element-X?type=design&node-id=5199-198932&mode=design&t=fTTvpuxYFjewYQOe-0
|
||||
*/
|
||||
val showUserDefinedSettingStyle: Boolean
|
||||
@@ -164,7 +165,7 @@ class RoomDetailsFlowNode @AssistedInject constructor(
|
||||
}
|
||||
|
||||
is NavTarget.RoomMemberDetails -> {
|
||||
val callback = object : RoomMemberDetailsNode.Callback {
|
||||
val callback = object : UserProfileNodeHelper.Callback {
|
||||
override fun openAvatarPreview(username: String, avatarUrl: String) {
|
||||
backstack.push(NavTarget.AvatarPreview(username, avatarUrl))
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
package io.element.android.features.roomdetails.impl
|
||||
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomState
|
||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState
|
||||
import io.element.android.features.userprofile.shared.UserProfileState
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
@@ -32,7 +32,7 @@ data class RoomDetailsState(
|
||||
val memberCount: Long,
|
||||
val isEncrypted: Boolean,
|
||||
val roomType: RoomDetailsType,
|
||||
val roomMemberDetailsState: RoomMemberDetailsState?,
|
||||
val roomMemberDetailsState: UserProfileState?,
|
||||
val canEdit: Boolean,
|
||||
val canInvite: Boolean,
|
||||
val canShowNotificationSettings: Boolean,
|
||||
|
||||
@@ -19,8 +19,8 @@ package io.element.android.features.roomdetails.impl
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomState
|
||||
import io.element.android.features.leaveroom.api.aLeaveRoomState
|
||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState
|
||||
import io.element.android.features.roomdetails.impl.members.details.aRoomMemberDetailsState
|
||||
import io.element.android.features.userprofile.shared.UserProfileState
|
||||
import io.element.android.features.userprofile.shared.aUserProfileState
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
@@ -90,7 +90,7 @@ fun aRoomDetailsState(
|
||||
canEdit: Boolean = false,
|
||||
canShowNotificationSettings: Boolean = true,
|
||||
roomType: RoomDetailsType = RoomDetailsType.Room,
|
||||
roomMemberDetailsState: RoomMemberDetailsState? = null,
|
||||
roomMemberDetailsState: UserProfileState? = null,
|
||||
leaveRoomState: LeaveRoomState = aLeaveRoomState(),
|
||||
roomNotificationSettings: RoomNotificationSettings = aRoomNotificationSettings(),
|
||||
isFavorite: Boolean = false,
|
||||
@@ -130,5 +130,5 @@ fun aDmRoomDetailsState(
|
||||
) = aRoomDetailsState(
|
||||
roomName = roomName,
|
||||
roomType = RoomDetailsType.Dm(aDmRoomMember(isIgnored = isDmMemberIgnored)),
|
||||
roomMemberDetailsState = aRoomMemberDetailsState()
|
||||
roomMemberDetailsState = aUserProfileState()
|
||||
)
|
||||
|
||||
@@ -49,10 +49,10 @@ import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomView
|
||||
import io.element.android.features.roomdetails.impl.blockuser.BlockUserDialogs
|
||||
import io.element.android.features.roomdetails.impl.blockuser.BlockUserSection
|
||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberHeaderSection
|
||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberMainActionsSection
|
||||
import io.element.android.features.userprofile.shared.UserProfileHeaderSection
|
||||
import io.element.android.features.userprofile.shared.UserProfileMainActionsSection
|
||||
import io.element.android.features.userprofile.shared.blockuser.BlockUserDialogs
|
||||
import io.element.android.features.userprofile.shared.blockuser.BlockUserSection
|
||||
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
|
||||
import io.element.android.libraries.designsystem.components.ClickableLinkText
|
||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||
@@ -143,15 +143,15 @@ fun RoomDetailsView(
|
||||
|
||||
is RoomDetailsType.Dm -> {
|
||||
val member = state.roomType.roomMember
|
||||
RoomMemberHeaderSection(
|
||||
UserProfileHeaderSection(
|
||||
avatarUrl = state.roomAvatarUrl ?: member.avatarUrl,
|
||||
userId = member.userId.value,
|
||||
userId = member.userId,
|
||||
userName = state.roomName,
|
||||
openAvatarPreview = { avatarUrl ->
|
||||
openAvatarPreview(member.getBestName(), avatarUrl)
|
||||
},
|
||||
)
|
||||
RoomMemberMainActionsSection(onShareUser = ::onShareMember)
|
||||
UserProfileMainActionsSection(onShareUser = ::onShareMember)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.height(18.dp))
|
||||
|
||||
@@ -28,8 +28,8 @@ import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.features.analytics.plan.MobileScreen
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.features.roomdetails.impl.R
|
||||
import io.element.android.libraries.androidutils.system.startSharePlainTextIntent
|
||||
import io.element.android.features.userprofile.shared.UserProfileNodeHelper
|
||||
import io.element.android.features.userprofile.shared.UserProfileView
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.NodeInputs
|
||||
import io.element.android.libraries.architecture.inputs
|
||||
@@ -38,8 +38,6 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import timber.log.Timber
|
||||
import io.element.android.libraries.androidutils.R as AndroidUtilsR
|
||||
|
||||
@ContributesNode(RoomScope::class)
|
||||
class RoomMemberDetailsNode @AssistedInject constructor(
|
||||
@@ -49,18 +47,14 @@ class RoomMemberDetailsNode @AssistedInject constructor(
|
||||
private val permalinkBuilder: PermalinkBuilder,
|
||||
presenterFactory: RoomMemberDetailsPresenter.Factory,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
interface Callback : NodeInputs {
|
||||
fun openAvatarPreview(username: String, avatarUrl: String)
|
||||
fun onStartDM(roomId: RoomId)
|
||||
}
|
||||
|
||||
data class RoomMemberDetailsInput(
|
||||
val roomMemberId: UserId
|
||||
) : NodeInputs
|
||||
|
||||
private val inputs = inputs<RoomMemberDetailsInput>()
|
||||
private val callback = inputs<Callback>()
|
||||
private val callback = inputs<UserProfileNodeHelper.Callback>()
|
||||
private val presenter = presenterFactory.create(inputs.roomMemberId)
|
||||
private val userProfileNodeHelper = UserProfileNodeHelper(inputs.roomMemberId)
|
||||
|
||||
init {
|
||||
lifecycle.subscribe(
|
||||
@@ -75,17 +69,7 @@ class RoomMemberDetailsNode @AssistedInject constructor(
|
||||
val context = LocalContext.current
|
||||
|
||||
fun onShareUser() {
|
||||
val permalinkResult = permalinkBuilder.permalinkForUser(inputs.roomMemberId)
|
||||
permalinkResult.onSuccess { permalink ->
|
||||
context.startSharePlainTextIntent(
|
||||
activityResultLauncher = null,
|
||||
chooserTitle = context.getString(R.string.screen_room_details_share_room_title),
|
||||
text = permalink,
|
||||
noActivityFoundMessage = context.getString(AndroidUtilsR.string.error_no_compatible_app_found)
|
||||
)
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
}
|
||||
userProfileNodeHelper.onShareUser(context, permalinkBuilder)
|
||||
}
|
||||
|
||||
fun onStartDM(roomId: RoomId) {
|
||||
@@ -95,11 +79,12 @@ class RoomMemberDetailsNode @AssistedInject constructor(
|
||||
val state = presenter.present()
|
||||
|
||||
LaunchedEffect(state.startDmActionState) {
|
||||
if (state.startDmActionState is AsyncAction.Success) {
|
||||
onStartDM(state.startDmActionState.data)
|
||||
val result = state.startDmActionState
|
||||
if (result is AsyncAction.Success) {
|
||||
onStartDM(result.data)
|
||||
}
|
||||
}
|
||||
RoomMemberDetailsView(
|
||||
UserProfileView(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
goBack = this::navigateUp,
|
||||
|
||||
@@ -28,7 +28,10 @@ import androidx.compose.runtime.setValue
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.features.createroom.api.StartDMAction
|
||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState.ConfirmationDialog
|
||||
import io.element.android.features.userprofile.shared.UserProfileEvents
|
||||
import io.element.android.features.userprofile.shared.UserProfilePresenterHelper
|
||||
import io.element.android.features.userprofile.shared.UserProfileState
|
||||
import io.element.android.features.userprofile.shared.UserProfileState.ConfirmationDialog
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
@@ -39,7 +42,6 @@ import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.room.getRoomMemberAsState
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
@@ -51,13 +53,18 @@ class RoomMemberDetailsPresenter @AssistedInject constructor(
|
||||
private val client: MatrixClient,
|
||||
private val room: MatrixRoom,
|
||||
private val startDMAction: StartDMAction,
|
||||
) : Presenter<RoomMemberDetailsState> {
|
||||
) : Presenter<UserProfileState> {
|
||||
interface Factory {
|
||||
fun create(roomMemberId: UserId): RoomMemberDetailsPresenter
|
||||
}
|
||||
|
||||
private val userProfilePresenterHelper = UserProfilePresenterHelper(
|
||||
userId = roomMemberId,
|
||||
client = client,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun present(): RoomMemberDetailsState {
|
||||
override fun present(): UserProfileState {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var confirmationDialog by remember { mutableStateOf<ConfirmationDialog?>(null) }
|
||||
val roomMember by room.getRoomMemberAsState(roomMemberId)
|
||||
@@ -81,34 +88,34 @@ class RoomMemberDetailsPresenter @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun handleEvents(event: RoomMemberDetailsEvents) {
|
||||
fun handleEvents(event: UserProfileEvents) {
|
||||
when (event) {
|
||||
is RoomMemberDetailsEvents.BlockUser -> {
|
||||
is UserProfileEvents.BlockUser -> {
|
||||
if (event.needsConfirmation) {
|
||||
confirmationDialog = ConfirmationDialog.Block
|
||||
} else {
|
||||
confirmationDialog = null
|
||||
coroutineScope.blockUser(roomMemberId, isBlocked)
|
||||
userProfilePresenterHelper.blockUser(coroutineScope, isBlocked)
|
||||
}
|
||||
}
|
||||
is RoomMemberDetailsEvents.UnblockUser -> {
|
||||
is UserProfileEvents.UnblockUser -> {
|
||||
if (event.needsConfirmation) {
|
||||
confirmationDialog = ConfirmationDialog.Unblock
|
||||
} else {
|
||||
confirmationDialog = null
|
||||
coroutineScope.unblockUser(roomMemberId, isBlocked)
|
||||
userProfilePresenterHelper.unblockUser(coroutineScope, isBlocked)
|
||||
}
|
||||
}
|
||||
RoomMemberDetailsEvents.ClearConfirmationDialog -> confirmationDialog = null
|
||||
RoomMemberDetailsEvents.ClearBlockUserError -> {
|
||||
UserProfileEvents.ClearConfirmationDialog -> confirmationDialog = null
|
||||
UserProfileEvents.ClearBlockUserError -> {
|
||||
isBlocked.value = AsyncData.Success(isBlocked.value.dataOrNull().orFalse())
|
||||
}
|
||||
RoomMemberDetailsEvents.StartDM -> {
|
||||
UserProfileEvents.StartDM -> {
|
||||
coroutineScope.launch {
|
||||
startDMAction.execute(roomMemberId, startDmActionState)
|
||||
}
|
||||
}
|
||||
RoomMemberDetailsEvents.ClearStartDMState -> {
|
||||
UserProfileEvents.ClearStartDMState -> {
|
||||
startDmActionState.value = AsyncAction.Uninitialized
|
||||
}
|
||||
}
|
||||
@@ -144,8 +151,8 @@ class RoomMemberDetailsPresenter @AssistedInject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
return RoomMemberDetailsState(
|
||||
userId = roomMemberId.value,
|
||||
return UserProfileState(
|
||||
userId = roomMemberId,
|
||||
userName = userName,
|
||||
avatarUrl = userAvatar,
|
||||
isBlocked = isBlocked.value,
|
||||
@@ -155,22 +162,4 @@ class RoomMemberDetailsPresenter @AssistedInject constructor(
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.blockUser(userId: UserId, isBlockedState: MutableState<AsyncData<Boolean>>) = launch {
|
||||
isBlockedState.value = AsyncData.Loading(false)
|
||||
client.ignoreUser(userId)
|
||||
.onFailure {
|
||||
isBlockedState.value = AsyncData.Failure(it, false)
|
||||
}
|
||||
// Note: on success, ignoredUserList will be updated.
|
||||
}
|
||||
|
||||
private fun CoroutineScope.unblockUser(userId: UserId, isBlockedState: MutableState<AsyncData<Boolean>>) = launch {
|
||||
isBlockedState.value = AsyncData.Loading(true)
|
||||
client.unignoreUser(userId)
|
||||
.onFailure {
|
||||
isBlockedState.value = AsyncData.Failure(it, true)
|
||||
}
|
||||
// Note: on success, ignoredUserList will be updated.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Заблакіраваць"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"Заблакіраваныя карыстальнікі не змогуць адпраўляць вам паведамленні, і ўсе іх паведамленні будуць схаваны. Вы можаце разблакіраваць іх у любы час."</string>
|
||||
<string name="screen_dm_details_block_user">"Заблакіраваць карыстальніка"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Разблакіраваць"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"Вы зноў зможаце ўбачыць усе паведамленні."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Разблакіраваць карыстальніка"</string>
|
||||
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Пры абнаўленні налад апавяшчэнняў адбылася памылка."</string>
|
||||
<string name="screen_notification_settings_mentions_only_disclaimer">"Ваш хатні сервер не падтрымлівае гэтую опцыю ў зашыфраваных пакоях, вы можаце не атрымаць апавяшчэнне ў некаторых пакоях."</string>
|
||||
<string name="screen_polls_history_title">"Апытанні"</string>
|
||||
@@ -117,5 +111,4 @@
|
||||
<string name="screen_room_roles_and_permissions_roles_header">"Ролі"</string>
|
||||
<string name="screen_room_roles_and_permissions_room_details">"Дэталі пакоя"</string>
|
||||
<string name="screen_room_roles_and_permissions_title">"Ролі і дазволы"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Пры спробе пачаць чат адбылася памылка"</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Блокиране"</string>
|
||||
<string name="screen_dm_details_block_user">"Блокиране на потребителя"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Отблокиране"</string>
|
||||
<string name="screen_dm_details_unblock_user">"Отблокиране на потребителя"</string>
|
||||
<string name="screen_polls_history_title">"Анкети"</string>
|
||||
<string name="screen_room_change_role_section_users">"Членове"</string>
|
||||
<string name="screen_room_details_add_topic_title">"Добавяне на тема"</string>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Zablokovat"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"Blokovaní uživatelé vám nebudou moci posílat zprávy a všechny jejich zprávy budou skryty. Můžete je kdykoli odblokovat."</string>
|
||||
<string name="screen_dm_details_block_user">"Zablokovat uživatele"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Odblokovat"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"Znovu uvidíte všechny zprávy od nich."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Odblokovat uživatele"</string>
|
||||
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Při aktualizaci nastavení oznámení došlo k chybě."</string>
|
||||
<string name="screen_notification_settings_mentions_only_disclaimer">"Váš domovský server tuto možnost v zašifrovaných místnostech nepodporuje, v některých místnostech nemusíte být upozorněni."</string>
|
||||
<string name="screen_polls_history_title">"Hlasování"</string>
|
||||
@@ -117,5 +111,4 @@
|
||||
<string name="screen_room_roles_and_permissions_roles_header">"Role"</string>
|
||||
<string name="screen_room_roles_and_permissions_room_details">"Podrobnosti místnosti"</string>
|
||||
<string name="screen_room_roles_and_permissions_title">"Role a oprávnění"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Při pokusu o zahájení chatu došlo k chybě"</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Blockieren"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"Blockierte Benutzer können Dir keine Nachrichten senden und alle ihre alten Nachrichten werden ausgeblendet. Die Blockierung kann jederzeit aufgehoben werden."</string>
|
||||
<string name="screen_dm_details_block_user">"Benutzer blockieren"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Blockierung aufheben"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"Der Nutzer kann dir wieder Nachrichten senden & alle Nachrichten des Nutzers werden wieder angezeigt."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Blockierung aufheben"</string>
|
||||
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Beim Aktualisieren der Benachrichtigungseinstellungen ist ein Fehler aufgetreten."</string>
|
||||
<string name="screen_notification_settings_mentions_only_disclaimer">"Dein Homeserver unterstützt diese Option in verschlüsselten Chat nicht. In einigen Chats wirst du möglicherweise nicht benachrichtigt."</string>
|
||||
<string name="screen_polls_history_title">"Umfragen"</string>
|
||||
@@ -20,15 +14,15 @@
|
||||
<string name="screen_room_change_permissions_remove_people">"Personen entfernen"</string>
|
||||
<string name="screen_room_change_permissions_room_avatar">"Avatar ändern"</string>
|
||||
<string name="screen_room_change_permissions_room_details">"Raum-Details anpassen"</string>
|
||||
<string name="screen_room_change_permissions_room_name">"Raum-Name ändern"</string>
|
||||
<string name="screen_room_change_permissions_room_topic">"Raum-Thema ändern"</string>
|
||||
<string name="screen_room_change_permissions_room_name">"Raumname ändern"</string>
|
||||
<string name="screen_room_change_permissions_room_topic">"Raumthema ändern"</string>
|
||||
<string name="screen_room_change_permissions_send_messages">"Nachrichten senden"</string>
|
||||
<string name="screen_room_change_role_administrators_title">"Admins bearbeiten"</string>
|
||||
<string name="screen_room_change_role_confirm_add_admin_description">"Du kannst diese Aktion nicht mehr rückgängig machen. Du vergibst dieselbe Rolle, wie auch Du sie hast."</string>
|
||||
<string name="screen_room_change_role_confirm_add_admin_description">"Du kannst diese Aktion nicht mehr rückgängig machen. Du vergibst dieselbe Rolle, die du auch hast."</string>
|
||||
<string name="screen_room_change_role_confirm_add_admin_title">"Als Administrator hinzufügen?"</string>
|
||||
<string name="screen_room_change_role_confirm_demote_self_action">"Zurückstufen"</string>
|
||||
<string name="screen_room_change_role_confirm_demote_self_description">"Du stufst dich selbst herab. Diese Änderung kann nicht rückgängig gemacht werden. Wenn du der letzte Benutzer mit dieser Rolle bist, ist es nicht möglich, diese Rolle wiederzuerlangen."</string>
|
||||
<string name="screen_room_change_role_confirm_demote_self_title">"Möchtest Du Dich selbst herabstufen?"</string>
|
||||
<string name="screen_room_change_role_confirm_demote_self_title">"Möchtest du dich selbst herabstufen?"</string>
|
||||
<string name="screen_room_change_role_invited_member_name">"%1$s (Ausstehend)"</string>
|
||||
<string name="screen_room_change_role_invited_member_name_android">"(Ausstehend)"</string>
|
||||
<string name="screen_room_change_role_moderators_admin_section_footer">"Administratoren haben automatisch Moderatorenrechte"</string>
|
||||
@@ -56,7 +50,7 @@
|
||||
<string name="screen_room_details_notification_mode_default">"Standard"</string>
|
||||
<string name="screen_room_details_notification_title">"Benachrichtigungen"</string>
|
||||
<string name="screen_room_details_roles_and_permissions">"Rollen und Berechtigungen"</string>
|
||||
<string name="screen_room_details_room_name_label">"Raum-Name"</string>
|
||||
<string name="screen_room_details_room_name_label">"Raumname"</string>
|
||||
<string name="screen_room_details_security_title">"Sicherheit"</string>
|
||||
<string name="screen_room_details_share_room_title">"Teilen"</string>
|
||||
<string name="screen_room_details_title">"Informationen"</string>
|
||||
@@ -116,5 +110,4 @@
|
||||
<string name="screen_room_roles_and_permissions_roles_header">"Rollen"</string>
|
||||
<string name="screen_room_roles_and_permissions_room_details">"Raum-Details anpassen"</string>
|
||||
<string name="screen_room_roles_and_permissions_title">"Rollen und Berechtigungen"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Beim Versuch, einen Chat zu starten, ist ein Fehler aufgetreten"</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Bloquear"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"Los usuarios bloqueados no podrán enviarte mensajes y todos sus mensajes se ocultarán. Puedes desbloquearlos cuando quieras."</string>
|
||||
<string name="screen_dm_details_block_user">"Bloquear usuario"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Desbloquear"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"Podrás ver todos sus mensajes de nuevo."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Desbloquear usuario"</string>
|
||||
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Se ha producido un error al actualizar la configuración de notificaciones."</string>
|
||||
<string name="screen_notification_settings_mentions_only_disclaimer">"Tu servidor principal no admite esta opción en salas cifradas, puede que no recibas notificaciones en algunas salas."</string>
|
||||
<string name="screen_polls_history_title">"Encuestas"</string>
|
||||
@@ -51,5 +45,4 @@
|
||||
<string name="screen_room_notification_settings_mode_all_messages">"Todos los mensajes"</string>
|
||||
<string name="screen_room_notification_settings_mode_mentions_and_keywords">"Únicamente Menciones y Palabras clave"</string>
|
||||
<string name="screen_room_notification_settings_room_custom_settings_title">"En esta sala, notificarme por"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Se ha producido un error al intentar iniciar un chat"</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Bloquer"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"Les utilisateurs bloqués ne pourront pas vous envoyer de messages et tous leurs messages seront masqués. Vous pouvez les débloquer à tout moment."</string>
|
||||
<string name="screen_dm_details_block_user">"Bloquer l’utilisateur"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Débloquer"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"Vous pourrez à nouveau voir tous ses messages."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Débloquer l’utilisateur"</string>
|
||||
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Une erreur s’est produite lors de la mise à jour du paramètre de notification."</string>
|
||||
<string name="screen_notification_settings_mentions_only_disclaimer">"Votre serveur d’accueil ne supporte pas cette option pour les salons chiffrés, vous pourriez ne pas être notifié(e) dans certains salons."</string>
|
||||
<string name="screen_polls_history_title">"Sondages"</string>
|
||||
@@ -116,5 +110,4 @@
|
||||
<string name="screen_room_roles_and_permissions_roles_header">"Rôles"</string>
|
||||
<string name="screen_room_roles_and_permissions_room_details">"Détails du salon"</string>
|
||||
<string name="screen_room_roles_and_permissions_title">"Rôles et autorisations"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Une erreur s’est produite lors de la tentative de création de la discussion"</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Letiltás"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"A letiltott felhasználók nem fognak tudni üzeneteket küldeni, és az összes üzenetük rejtve lesz. Bármikor feloldhatja a letiltásukat."</string>
|
||||
<string name="screen_dm_details_block_user">"Felhasználó letiltása"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Letiltás feloldása"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"Újra láthatja az összes üzenetét."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Felhasználó kitiltásának feloldása"</string>
|
||||
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Hiba történt az értesítési beállítás frissítésekor."</string>
|
||||
<string name="screen_notification_settings_mentions_only_disclaimer">"A Matrix-kiszolgálója nem támogatja ezt a beállítást a titkosított szobákban, előfordulhat, hogy egyes szobákban nem kap értesítést."</string>
|
||||
<string name="screen_polls_history_title">"Szavazások"</string>
|
||||
@@ -116,5 +110,4 @@
|
||||
<string name="screen_room_roles_and_permissions_roles_header">"Szerepkörök"</string>
|
||||
<string name="screen_room_roles_and_permissions_room_details">"Szoba részletei"</string>
|
||||
<string name="screen_room_roles_and_permissions_title">"Szerepkörök és jogosultságok"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Hiba történt a csevegés indításakor"</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Blokir"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"Pengguna yang diblokir tidak akan dapat mengirim Anda pesan dan semua pesan mereka akan disembunyikan. Anda dapat membuka blokirnya kapan saja."</string>
|
||||
<string name="screen_dm_details_block_user">"Blokir pengguna"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Buka blokir"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"Anda akan dapat melihat semua pesan dari mereka lagi."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Buka blokir pengguna"</string>
|
||||
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Terjadi kesalahan saat memperbarui pengaturan pemberitahuan."</string>
|
||||
<string name="screen_notification_settings_mentions_only_disclaimer">"Homeserver Anda tidak mendukung opsi ini dalam ruangan terenkripsi, Anda mungkin tidak diberi tahu dalam beberapa ruangan."</string>
|
||||
<string name="screen_polls_history_title">"Pemungutan suara"</string>
|
||||
@@ -115,5 +109,4 @@
|
||||
<string name="screen_room_roles_and_permissions_roles_header">"Peran"</string>
|
||||
<string name="screen_room_roles_and_permissions_room_details">"Detail ruangan"</string>
|
||||
<string name="screen_room_roles_and_permissions_title">"Peran dan perizinan"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Terjadi kesalahan saat mencoba memulai obrolan"</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Blocca"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"Gli utenti bloccati non saranno in grado di inviarti messaggi e tutti quelli già ricevuti saranno nascosti. Puoi sbloccarli in qualsiasi momento."</string>
|
||||
<string name="screen_dm_details_block_user">"Blocca utente"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Sblocca"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"Potrai vedere di nuovo tutti i suoi messaggi."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Sblocca utente"</string>
|
||||
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Si è verificato un errore durante l\'aggiornamento delle impostazioni di notifica."</string>
|
||||
<string name="screen_notification_settings_mentions_only_disclaimer">"Il tuo homeserver non supporta questa opzione nelle stanze crifrate, quindi potresti non ricevere notifiche in alcune stanze."</string>
|
||||
<string name="screen_polls_history_title">"Sondaggi"</string>
|
||||
@@ -113,5 +107,4 @@
|
||||
<string name="screen_room_roles_and_permissions_roles_header">"Ruoli"</string>
|
||||
<string name="screen_room_roles_and_permissions_room_details">"Dettagli della stanza"</string>
|
||||
<string name="screen_room_roles_and_permissions_title">"Ruoli e autorizzazioni"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Si è verificato un errore durante il tentativo di avviare una chat"</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Blocați"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"Utilizatorii blocați nu vă vor putea trimite mesaje și toate mesajele lor vor fi ascunse. Puteți anula această acțiune oricând."</string>
|
||||
<string name="screen_dm_details_block_user">"Blocați utilizatorul"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Deblocați"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"La deblocarea utilizatorului, veți putea vedea din nou toate mesajele de la acesta."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Deblocați utilizatorul"</string>
|
||||
<string name="screen_notification_settings_edit_failed_updating_default_mode">"A apărut o eroare în timpul actualizării setărilor pentru notificari."</string>
|
||||
<string name="screen_notification_settings_mentions_only_disclaimer">"Serverul dumneavoastră nu acceptă această opțiune în camerele criptate, este posibil să nu primiți notificări în unele camere."</string>
|
||||
<string name="screen_polls_history_title">"Sondaje"</string>
|
||||
@@ -63,5 +57,4 @@
|
||||
<string name="screen_room_notification_settings_mode_all_messages">"Toate mesajele"</string>
|
||||
<string name="screen_room_notification_settings_mode_mentions_and_keywords">"Numai mențiuni și cuvinte cheie"</string>
|
||||
<string name="screen_room_notification_settings_room_custom_settings_title">"În această cameră, anunțați-mă pentru"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"A apărut o eroare la încercarea începerii conversației"</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Заблокировать"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"Заблокированные пользователи не смогут отправлять вам сообщения, а все их сообщения будут скрыты. Вы можете разблокировать их в любое время."</string>
|
||||
<string name="screen_dm_details_block_user">"Заблокировать пользователя"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Разблокировать"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"Вы снова сможете увидеть все сообщения."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Разблокировать пользователя"</string>
|
||||
<string name="screen_notification_settings_edit_failed_updating_default_mode">"При обновлении настроек уведомления произошла ошибка."</string>
|
||||
<string name="screen_notification_settings_mentions_only_disclaimer">"Ваш домашний сервер не поддерживает эту опцию в зашифрованных комнатах, в некоторых комнатах вы можете не получать уведомления."</string>
|
||||
<string name="screen_polls_history_title">"Опросы"</string>
|
||||
@@ -117,5 +111,4 @@
|
||||
<string name="screen_room_roles_and_permissions_roles_header">"Роли"</string>
|
||||
<string name="screen_room_roles_and_permissions_room_details">"Информация о комнате"</string>
|
||||
<string name="screen_room_roles_and_permissions_title">"Роли и разрешения"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Произошла ошибка при попытке открытия комнаты"</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Zablokovať"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"Blokovaní používatelia vám nebudú môcť posielať správy a všetky ich správy budú skryté. Môžete ich kedykoľvek odblokovať."</string>
|
||||
<string name="screen_dm_details_block_user">"Zablokovať používateľa"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Odblokovať"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"Všetky správy od nich budete môcť opäť vidieť."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Odblokovať používateľa"</string>
|
||||
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Pri aktualizácii nastavenia oznámenia došlo k chybe."</string>
|
||||
<string name="screen_notification_settings_mentions_only_disclaimer">"Váš domovský server nepodporuje túto možnosť v šifrovaných miestnostiach, v niektorých miestnostiach nemusíte dostať upozornenie."</string>
|
||||
<string name="screen_polls_history_title">"Ankety"</string>
|
||||
@@ -117,5 +111,4 @@
|
||||
<string name="screen_room_roles_and_permissions_roles_header">"Roly"</string>
|
||||
<string name="screen_room_roles_and_permissions_room_details">"Podrobnosti o miestnosti"</string>
|
||||
<string name="screen_room_roles_and_permissions_title">"Roly a povolenia"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Pri pokuse o spustenie konverzácie sa vyskytla chyba"</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Blockera"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"Blockerade användare kommer inte att kunna skicka meddelanden till dig och alla deras meddelanden kommer att döljas. Du kan avblockera dem när som helst."</string>
|
||||
<string name="screen_dm_details_block_user">"Blockera användare"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Avblockera"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"Du kommer att kunna se alla meddelanden från dem igen."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Avblockera användare"</string>
|
||||
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Ett fel uppstod vid uppdatering av aviseringsinställningen."</string>
|
||||
<string name="screen_room_change_permissions_everyone">"Alla"</string>
|
||||
<string name="screen_room_details_add_topic_title">"Lägg till ämne"</string>
|
||||
@@ -48,5 +42,4 @@
|
||||
<string name="screen_room_notification_settings_mode_all_messages">"Alla meddelanden"</string>
|
||||
<string name="screen_room_notification_settings_mode_mentions_and_keywords">"Endast omnämnanden och nyckelord"</string>
|
||||
<string name="screen_room_notification_settings_room_custom_settings_title">"I det här rummet, meddela mig för"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Ett fel uppstod när du försökte starta en chatt"</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Заблокувати"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"Заблоковані користувачі не зможуть надсилати Вам повідомлення, і всі їхні повідомлення будуть приховані. Ви можете розблокувати їх у будь-який час."</string>
|
||||
<string name="screen_dm_details_block_user">"Заблокувати користувача"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Розблокувати"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"Ви знову зможете бачити всі повідомлення від них."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Розблокувати користувача"</string>
|
||||
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Під час оновлення налаштувань сповіщень сталася помилка."</string>
|
||||
<string name="screen_notification_settings_mentions_only_disclaimer">"Ваш домашній сервер не підтримує цю опцію в зашифрованих кімнатах, ви можете не отримати сповіщення в деяких кімнатах."</string>
|
||||
<string name="screen_polls_history_title">"Опитування"</string>
|
||||
@@ -113,5 +107,4 @@
|
||||
<string name="screen_room_roles_and_permissions_roles_header">"Ролі"</string>
|
||||
<string name="screen_room_roles_and_permissions_room_details">"Деталі кімнати"</string>
|
||||
<string name="screen_room_roles_and_permissions_title">"Ролі та дозволи"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Під час спроби почати чат сталася помилка"</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"封鎖"</string>
|
||||
<string name="screen_dm_details_block_user">"封鎖使用者"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"解除封鎖"</string>
|
||||
<string name="screen_dm_details_unblock_user">"解除封鎖使用者"</string>
|
||||
<string name="screen_notification_settings_edit_failed_updating_default_mode">"更新通知設定時發生錯誤。"</string>
|
||||
<string name="screen_polls_history_title">"所有投票"</string>
|
||||
<string name="screen_room_change_permissions_everyone">"所有人"</string>
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Block"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"Blocked users won\'t be able to send you messages and all their messages will be hidden. You can unblock them anytime."</string>
|
||||
<string name="screen_dm_details_block_user">"Block user"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Unblock"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"You\'ll be able to see all messages from them again."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Unblock user"</string>
|
||||
<string name="screen_notification_settings_edit_failed_updating_default_mode">"An error occurred while updating the notification setting."</string>
|
||||
<string name="screen_notification_settings_mentions_only_disclaimer">"Your homeserver does not support this option in encrypted rooms, you may not get notified in some rooms."</string>
|
||||
<string name="screen_polls_history_title">"Polls"</string>
|
||||
@@ -116,5 +110,4 @@
|
||||
<string name="screen_room_roles_and_permissions_roles_header">"Roles"</string>
|
||||
<string name="screen_room_roles_and_permissions_room_details">"Room details"</string>
|
||||
<string name="screen_room_roles_and_permissions_title">"Roles and permissions"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"An error occurred when trying to start a chat"</string>
|
||||
</resources>
|
||||
|
||||
@@ -25,9 +25,9 @@ import io.element.android.features.createroom.api.StartDMAction
|
||||
import io.element.android.features.createroom.test.FakeStartDMAction
|
||||
import io.element.android.features.roomdetails.aMatrixRoom
|
||||
import io.element.android.features.roomdetails.impl.members.aRoomMember
|
||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsEvents
|
||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter
|
||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState
|
||||
import io.element.android.features.userprofile.shared.UserProfileEvents
|
||||
import io.element.android.features.userprofile.shared.UserProfileState
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
@@ -66,7 +66,7 @@ class RoomMemberDetailsPresenterTests {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
assertThat(initialState.userId).isEqualTo(roomMember.userId.value)
|
||||
assertThat(initialState.userId).isEqualTo(roomMember.userId)
|
||||
assertThat(initialState.userName).isEqualTo(roomMember.displayName)
|
||||
assertThat(initialState.avatarUrl).isEqualTo(roomMember.avatarUrl)
|
||||
assertThat(initialState.isBlocked).isEqualTo(AsyncData.Success(roomMember.isIgnored))
|
||||
@@ -157,12 +157,12 @@ class RoomMemberDetailsPresenterTests {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
initialState.eventSink(RoomMemberDetailsEvents.BlockUser(needsConfirmation = true))
|
||||
initialState.eventSink(UserProfileEvents.BlockUser(needsConfirmation = true))
|
||||
|
||||
val dialogState = awaitItem()
|
||||
assertThat(dialogState.displayConfirmationDialog).isEqualTo(RoomMemberDetailsState.ConfirmationDialog.Block)
|
||||
assertThat(dialogState.displayConfirmationDialog).isEqualTo(UserProfileState.ConfirmationDialog.Block)
|
||||
|
||||
dialogState.eventSink(RoomMemberDetailsEvents.ClearConfirmationDialog)
|
||||
dialogState.eventSink(UserProfileEvents.ClearConfirmationDialog)
|
||||
assertThat(awaitItem().displayConfirmationDialog).isNull()
|
||||
|
||||
ensureAllEventsConsumed()
|
||||
@@ -181,12 +181,12 @@ class RoomMemberDetailsPresenterTests {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
initialState.eventSink(RoomMemberDetailsEvents.BlockUser(needsConfirmation = false))
|
||||
initialState.eventSink(UserProfileEvents.BlockUser(needsConfirmation = false))
|
||||
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
|
||||
client.emitIgnoreUserList(listOf(roomMember.userId))
|
||||
assertThat(awaitItem().isBlocked.dataOrNull()).isTrue()
|
||||
|
||||
initialState.eventSink(RoomMemberDetailsEvents.UnblockUser(needsConfirmation = false))
|
||||
initialState.eventSink(UserProfileEvents.UnblockUser(needsConfirmation = false))
|
||||
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
|
||||
client.emitIgnoreUserList(listOf())
|
||||
assertThat(awaitItem().isBlocked.dataOrNull()).isFalse()
|
||||
@@ -202,12 +202,12 @@ class RoomMemberDetailsPresenterTests {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
initialState.eventSink(RoomMemberDetailsEvents.BlockUser(needsConfirmation = false))
|
||||
initialState.eventSink(UserProfileEvents.BlockUser(needsConfirmation = false))
|
||||
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
|
||||
val errorState = awaitItem()
|
||||
assertThat(errorState.isBlocked.errorOrNull()).isEqualTo(A_THROWABLE)
|
||||
// Clear error
|
||||
initialState.eventSink(RoomMemberDetailsEvents.ClearBlockUserError)
|
||||
initialState.eventSink(UserProfileEvents.ClearBlockUserError)
|
||||
assertThat(awaitItem().isBlocked).isEqualTo(AsyncData.Success(false))
|
||||
}
|
||||
}
|
||||
@@ -221,12 +221,12 @@ class RoomMemberDetailsPresenterTests {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
initialState.eventSink(RoomMemberDetailsEvents.UnblockUser(needsConfirmation = false))
|
||||
initialState.eventSink(UserProfileEvents.UnblockUser(needsConfirmation = false))
|
||||
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
|
||||
val errorState = awaitItem()
|
||||
assertThat(errorState.isBlocked.errorOrNull()).isEqualTo(A_THROWABLE)
|
||||
// Clear error
|
||||
initialState.eventSink(RoomMemberDetailsEvents.ClearBlockUserError)
|
||||
initialState.eventSink(UserProfileEvents.ClearBlockUserError)
|
||||
assertThat(awaitItem().isBlocked).isEqualTo(AsyncData.Success(true))
|
||||
}
|
||||
}
|
||||
@@ -238,12 +238,12 @@ class RoomMemberDetailsPresenterTests {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
initialState.eventSink(RoomMemberDetailsEvents.UnblockUser(needsConfirmation = true))
|
||||
initialState.eventSink(UserProfileEvents.UnblockUser(needsConfirmation = true))
|
||||
|
||||
val dialogState = awaitItem()
|
||||
assertThat(dialogState.displayConfirmationDialog).isEqualTo(RoomMemberDetailsState.ConfirmationDialog.Unblock)
|
||||
assertThat(dialogState.displayConfirmationDialog).isEqualTo(UserProfileState.ConfirmationDialog.Unblock)
|
||||
|
||||
dialogState.eventSink(RoomMemberDetailsEvents.ClearConfirmationDialog)
|
||||
dialogState.eventSink(UserProfileEvents.ClearConfirmationDialog)
|
||||
assertThat(awaitItem().displayConfirmationDialog).isNull()
|
||||
|
||||
ensureAllEventsConsumed()
|
||||
@@ -264,18 +264,18 @@ class RoomMemberDetailsPresenterTests {
|
||||
|
||||
// Failure
|
||||
startDMAction.givenExecuteResult(startDMFailureResult)
|
||||
initialState.eventSink(RoomMemberDetailsEvents.StartDM)
|
||||
initialState.eventSink(UserProfileEvents.StartDM)
|
||||
assertThat(awaitItem().startDmActionState).isInstanceOf(AsyncAction.Loading::class.java)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.startDmActionState).isEqualTo(startDMFailureResult)
|
||||
state.eventSink(RoomMemberDetailsEvents.ClearStartDMState)
|
||||
state.eventSink(UserProfileEvents.ClearStartDMState)
|
||||
}
|
||||
|
||||
// Success
|
||||
startDMAction.givenExecuteResult(startDMSuccessResult)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.startDmActionState).isEqualTo(AsyncAction.Uninitialized)
|
||||
state.eventSink(RoomMemberDetailsEvents.StartDM)
|
||||
state.eventSink(UserProfileEvents.StartDM)
|
||||
}
|
||||
assertThat(awaitItem().startDmActionState).isInstanceOf(AsyncAction.Loading::class.java)
|
||||
awaitItem().also { state ->
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="confirm_recovery_key_banner_message">"Din chattsäkerhetskopia är för närvarande inte synkroniserad. Du måste ange din återställningsnyckel för att behålla åtkomsten till din chattsäkerhetskopia."</string>
|
||||
<string name="confirm_recovery_key_banner_title">"Ange din återställningsnyckel"</string>
|
||||
<string name="screen_invites_decline_chat_message">"Är du säker på att du vill tacka nej till inbjudan att gå med%1$s?"</string>
|
||||
<string name="screen_invites_decline_chat_title">"Avböj inbjudan"</string>
|
||||
<string name="screen_invites_decline_direct_chat_message">"Är du säker på att du vill avböja denna privata chatt med %1$s?"</string>
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
<string name="screen_roomlist_a11y_create_message">"建立新的對話或聊天室"</string>
|
||||
<string name="screen_roomlist_filter_favourites">"我的最愛"</string>
|
||||
<string name="screen_roomlist_filter_people">"夥伴"</string>
|
||||
<string name="screen_roomlist_filter_rooms">"聊天室"</string>
|
||||
<string name="screen_roomlist_filter_unreads">"未讀"</string>
|
||||
<string name="screen_roomlist_main_space_title">"所有聊天室"</string>
|
||||
<string name="session_verification_banner_message">"您似乎正在使用新的裝置。請使用另一個裝置進行驗證,以存取您的加密訊息。"</string>
|
||||
<string name="session_verification_banner_title">"驗證這是您本人"</string>
|
||||
|
||||
@@ -31,13 +31,14 @@
|
||||
</string>
|
||||
<string name="screen_key_backup_disable_confirmation_action_turn_off">"Ausschalten"</string>
|
||||
<string name="screen_key_backup_disable_confirmation_description">"Du verlierst deine verschlüsselten Nachrichten, wenn du auf allen Geräten abgemeldet bist."</string>
|
||||
<string name="screen_key_backup_disable_confirmation_title">"Bist du sicher, dass du das Backup ausschalten willst?"</string>
|
||||
<string name="screen_key_backup_disable_description">"Wenn du das Backup ausschaltest, wird dein aktuelles Backup des Verschlüsselungsschlüssels entfernt und andere Sicherheitsfunktionen werden deaktiviert. In diesem Fall wirst du:"</string>
|
||||
<string name="screen_key_backup_disable_description_point_1">"Keine Historie für verschlüsselte Nachrichten auf neuen Geräten"</string>
|
||||
<string name="screen_key_backup_disable_confirmation_title">"Bist du sicher, dass du das Backup deaktivieren willst?"</string>
|
||||
<string name="screen_key_backup_disable_description">"Wenn du das Backup deaktivierst, wird dein aktuelles Backup des Verschlüsselungsschlüssels entfernt und andere Sicherheitsfunktionen werden deaktiviert.
|
||||
Das bedeutet:"</string>
|
||||
<string name="screen_key_backup_disable_description_point_1">"Keine Historie für verschlüsselte Nachrichten auf neuen Geräten ."</string>
|
||||
<string name="screen_key_backup_disable_description_point_2">"Du verlierst den Zugriff auf deine verschlüsselten Nachrichten, wenn du dich überall von %1$s abmeldest"</string>
|
||||
<string name="screen_key_backup_disable_title">"Bist du sicher, dass du das Backup ausschalten willst?"</string>
|
||||
<string name="screen_recovery_key_change_description">"Besorge dir einen neuen Wiederherstellungsschlüssel, wenn du deinen alten verloren hast. Nachdem du deinen Wiederherstellungsschlüssel geändert hast, funktioniert dein alter Schlüssel nicht mehr."</string>
|
||||
<string name="screen_recovery_key_change_generate_key">"Erstelle einen neuen Wiederherstellungsschlüssel"</string>
|
||||
<string name="screen_key_backup_disable_title">"Bist du sicher, dass du das Backup deaktivieren willst?"</string>
|
||||
<string name="screen_recovery_key_change_description">"Hier kannst Du einen neuen Wiederherstellungsschlüssel erstellen. Nachdem Du einen neuen Wiederherstellungsschlüssel erstellt hast, funktioniert dein alter Schlüssel nicht mehr."</string>
|
||||
<string name="screen_recovery_key_change_generate_key">"Wiederherstellungsschlüssel erstellen"</string>
|
||||
<string name="screen_recovery_key_change_generate_key_description">"Stelle sicher, dass du deinen Wiederherstellungsschlüssel an einem sicheren Ort aufbewahren kannst"</string>
|
||||
<string name="screen_recovery_key_change_success">"Wiederherstellungsschlüssel geändert"</string>
|
||||
<string name="screen_recovery_key_change_title">"Wiederherstellungsschlüssel ändern?"</string>
|
||||
@@ -55,7 +56,7 @@
|
||||
" oder Passcode"
|
||||
</string>
|
||||
<string name="screen_recovery_key_confirm_key_placeholder">"Eingeben…"</string>
|
||||
<string name="screen_recovery_key_confirm_lost_recovery_key">"Hast Du Deinen Wiederherstellungschlüssel vergessen?"</string>
|
||||
<string name="screen_recovery_key_confirm_lost_recovery_key">"Hast du deinen Wiederherstellungschlüssel vergessen?"</string>
|
||||
<string name="screen_recovery_key_confirm_success">"Wiederherstellungsschlüssel bestätigt"</string>
|
||||
<string name="screen_recovery_key_confirm_title">"Bitte Wiederherstellungsschlüssel eingeben"</string>
|
||||
<string name="screen_recovery_key_copied_to_clipboard">"Wiederherstellungsschlüssel kopiert"</string>
|
||||
|
||||
@@ -22,10 +22,13 @@
|
||||
<string name="screen_recovery_key_change_success">"Återställningsnyckel ändrad"</string>
|
||||
<string name="screen_recovery_key_change_title">"Byt återställningsnyckel?"</string>
|
||||
<string name="screen_recovery_key_confirm_description">"Se till att ingen kan se den här skärmen"</string>
|
||||
<string name="screen_recovery_key_confirm_error_title">"Felaktig återställningsnyckel"</string>
|
||||
<string name="screen_recovery_key_confirm_key_description">"Om du har en säkerhetsnyckel eller säkerhetsfras så funkar den också."</string>
|
||||
<string name="screen_recovery_key_confirm_key_placeholder">"Ange …"</string>
|
||||
<string name="screen_recovery_key_confirm_success">"Återställningsnyckel bekräftad"</string>
|
||||
<string name="screen_recovery_key_confirm_title">"Ange din återställningsnyckel"</string>
|
||||
<string name="screen_recovery_key_copied_to_clipboard">"Kopierade återställningsnyckel"</string>
|
||||
<string name="screen_recovery_key_generating_key">"Genererar …"</string>
|
||||
<string name="screen_recovery_key_save_action">"Spara återställningsnyckeln"</string>
|
||||
<string name="screen_recovery_key_save_description">"Skriv ner din återställningsnyckel någonstans säkert eller spara den i en lösenordshanterare."</string>
|
||||
<string name="screen_recovery_key_save_key_description">"Tryck för att kopiera återställningsnyckeln"</string>
|
||||
|
||||
29
features/userprofile/api/build.gradle.kts
Normal file
29
features/userprofile/api/build.gradle.kts
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-library")
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.userprofile.api"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.userprofile.api
|
||||
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||
import io.element.android.libraries.architecture.NodeInputs
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
|
||||
interface UserProfileEntryPoint : FeatureEntryPoint {
|
||||
data class Params(val userId: UserId) : NodeInputs
|
||||
|
||||
interface Callback : Plugin {
|
||||
fun onOpenRoom(roomId: RoomId)
|
||||
}
|
||||
|
||||
interface NodeBuilder {
|
||||
fun params(params: Params): NodeBuilder
|
||||
fun callback(callback: Callback): NodeBuilder
|
||||
fun build(): Node
|
||||
}
|
||||
|
||||
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
|
||||
}
|
||||
69
features/userprofile/impl/build.gradle.kts
Normal file
69
features/userprofile/impl/build.gradle.kts
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
alias(libs.plugins.anvil)
|
||||
alias(libs.plugins.ksp)
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.userprofile.impl"
|
||||
testOptions {
|
||||
unitTests {
|
||||
isIncludeAndroidResources = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
anvil {
|
||||
generateDaggerFactories.set(true)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
anvil(projects.anvilcodegen)
|
||||
implementation(projects.anvilannotations)
|
||||
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.matrixui)
|
||||
implementation(projects.libraries.designsystem)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
implementation(projects.libraries.androidutils)
|
||||
implementation(projects.libraries.mediaviewer.api)
|
||||
api(projects.features.userprofile.api)
|
||||
api(projects.features.userprofile.shared)
|
||||
implementation(libs.coil.compose)
|
||||
implementation(projects.features.createroom.api)
|
||||
implementation(projects.services.analytics.api)
|
||||
|
||||
testImplementation(libs.test.junit)
|
||||
testImplementation(libs.coroutines.test)
|
||||
testImplementation(libs.molecule.runtime)
|
||||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.test.turbine)
|
||||
testImplementation(libs.test.mockk)
|
||||
testImplementation(libs.test.robolectric)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.features.createroom.test)
|
||||
testImplementation(projects.tests.testutils)
|
||||
testImplementation(libs.androidx.compose.ui.test.junit)
|
||||
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
||||
|
||||
ksp(libs.showkase.processor)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.userprofile.impl
|
||||
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.userprofile.api.UserProfileEntryPoint
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultUserProfileEntryPoint @Inject constructor() : UserProfileEntryPoint {
|
||||
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): UserProfileEntryPoint.NodeBuilder {
|
||||
return object : UserProfileEntryPoint.NodeBuilder {
|
||||
val plugins = ArrayList<Plugin>()
|
||||
|
||||
override fun params(params: UserProfileEntryPoint.Params): UserProfileEntryPoint.NodeBuilder {
|
||||
plugins += params
|
||||
return this
|
||||
}
|
||||
|
||||
override fun callback(callback: UserProfileEntryPoint.Callback): UserProfileEntryPoint.NodeBuilder {
|
||||
plugins += callback
|
||||
return this
|
||||
}
|
||||
|
||||
override fun build(): Node {
|
||||
return parentNode.createNode<UserProfileFlowNode>(buildContext, plugins)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.userprofile.impl
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import com.bumble.appyx.core.plugin.plugins
|
||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||
import com.bumble.appyx.navmodel.backstack.operation.push
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.features.userprofile.api.UserProfileEntryPoint
|
||||
import io.element.android.features.userprofile.shared.avatar.AvatarPreviewNode
|
||||
import io.element.android.features.userprofile.impl.root.UserProfileNode
|
||||
import io.element.android.features.userprofile.shared.UserProfileNodeHelper
|
||||
import io.element.android.libraries.architecture.BackstackView
|
||||
import io.element.android.libraries.architecture.BaseFlowNode
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
import io.element.android.libraries.architecture.inputs
|
||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.libraries.mediaviewer.api.local.MediaInfo
|
||||
import io.element.android.libraries.mediaviewer.api.viewer.MediaViewerNode
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
class UserProfileFlowNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
) : BaseFlowNode<UserProfileFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
initialElement = NavTarget.Root,
|
||||
savedStateMap = buildContext.savedStateMap,
|
||||
),
|
||||
buildContext = buildContext,
|
||||
plugins = plugins,
|
||||
) {
|
||||
sealed interface NavTarget : Parcelable {
|
||||
@Parcelize
|
||||
data object Root : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data class AvatarPreview(val name: String, val avatarUrl: String) : NavTarget
|
||||
}
|
||||
|
||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
return when (navTarget) {
|
||||
NavTarget.Root -> {
|
||||
val callback = object : UserProfileNodeHelper.Callback {
|
||||
override fun openAvatarPreview(username: String, avatarUrl: String) {
|
||||
backstack.push(NavTarget.AvatarPreview(username, avatarUrl))
|
||||
}
|
||||
|
||||
override fun onStartDM(roomId: RoomId) {
|
||||
plugins<UserProfileEntryPoint.Callback>().forEach { it.onOpenRoom(roomId) }
|
||||
}
|
||||
}
|
||||
val params = UserProfileNode.UserProfileInputs(userId = inputs<UserProfileEntryPoint.Params>().userId)
|
||||
createNode<UserProfileNode>(buildContext, listOf(callback, params))
|
||||
}
|
||||
is NavTarget.AvatarPreview -> {
|
||||
// We need to fake the MimeType here for the viewer to work.
|
||||
val mimeType = MimeTypes.Images
|
||||
val input = MediaViewerNode.Inputs(
|
||||
mediaInfo = MediaInfo(
|
||||
name = navTarget.name,
|
||||
mimeType = mimeType,
|
||||
formattedFileSize = "",
|
||||
fileExtension = ""
|
||||
),
|
||||
mediaSource = MediaSource(url = navTarget.avatarUrl),
|
||||
thumbnailSource = null,
|
||||
canDownload = false,
|
||||
canShare = false,
|
||||
)
|
||||
createNode<AvatarPreviewNode>(buildContext, listOf(input))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
BackstackView()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.userprofile.impl.di
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesTo
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import io.element.android.features.createroom.api.StartDMAction
|
||||
import io.element.android.features.userprofile.impl.root.UserProfilePresenter
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
|
||||
@Module
|
||||
@ContributesTo(SessionScope::class)
|
||||
object UserProfileModule {
|
||||
@Provides
|
||||
fun provideUserProfilePresenterFactory(
|
||||
matrixClient: MatrixClient,
|
||||
startDMAction: StartDMAction,
|
||||
): UserProfilePresenter.Factory {
|
||||
return object : UserProfilePresenter.Factory {
|
||||
override fun create(userId: UserId): UserProfilePresenter {
|
||||
return UserProfilePresenter(userId, matrixClient, startDMAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.userprofile.impl.root
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.bumble.appyx.core.lifecycle.subscribe
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import im.vector.app.features.analytics.plan.MobileScreen
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.features.userprofile.shared.UserProfileNodeHelper
|
||||
import io.element.android.features.userprofile.shared.UserProfileView
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.NodeInputs
|
||||
import io.element.android.libraries.architecture.inputs
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
class UserProfileNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val permalinkBuilder: PermalinkBuilder,
|
||||
presenterFactory: UserProfilePresenter.Factory,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
data class UserProfileInputs(
|
||||
val userId: UserId
|
||||
) : NodeInputs
|
||||
|
||||
private val inputs = inputs<UserProfileInputs>()
|
||||
private val callback = inputs<UserProfileNodeHelper.Callback>()
|
||||
private val presenter = presenterFactory.create(inputs.userId)
|
||||
private val userProfileNodeHelper = UserProfileNodeHelper(inputs.userId)
|
||||
|
||||
init {
|
||||
lifecycle.subscribe(
|
||||
onResume = {
|
||||
analyticsService.screen(MobileScreen(screenName = MobileScreen.ScreenName.User))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val context = LocalContext.current
|
||||
|
||||
fun onShareUser() {
|
||||
userProfileNodeHelper.onShareUser(context, permalinkBuilder)
|
||||
}
|
||||
|
||||
fun onStartDM(roomId: RoomId) {
|
||||
callback.onStartDM(roomId)
|
||||
}
|
||||
|
||||
val state = presenter.present()
|
||||
|
||||
LaunchedEffect(state.startDmActionState) {
|
||||
val result = state.startDmActionState
|
||||
if (result is AsyncAction.Success) {
|
||||
onStartDM(result.data)
|
||||
}
|
||||
}
|
||||
UserProfileView(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
goBack = this::navigateUp,
|
||||
onShareUser = ::onShareUser,
|
||||
onDMStarted = ::onStartDM,
|
||||
openAvatarPreview = callback::openAvatarPreview,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.userprofile.impl.root
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.features.createroom.api.StartDMAction
|
||||
import io.element.android.features.userprofile.shared.UserProfileEvents
|
||||
import io.element.android.features.userprofile.shared.UserProfilePresenterHelper
|
||||
import io.element.android.features.userprofile.shared.UserProfileState
|
||||
import io.element.android.features.userprofile.shared.UserProfileState.ConfirmationDialog
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.bool.orFalse
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class UserProfilePresenter @AssistedInject constructor(
|
||||
@Assisted private val userId: UserId,
|
||||
private val client: MatrixClient,
|
||||
private val startDMAction: StartDMAction,
|
||||
) : Presenter<UserProfileState> {
|
||||
interface Factory {
|
||||
fun create(userId: UserId): UserProfilePresenter
|
||||
}
|
||||
|
||||
private val userProfilePresenterHelper = UserProfilePresenterHelper(
|
||||
userId = userId,
|
||||
client = client,
|
||||
)
|
||||
|
||||
@Composable
|
||||
override fun present(): UserProfileState {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var confirmationDialog by remember { mutableStateOf<ConfirmationDialog?>(null) }
|
||||
var userProfile by remember { mutableStateOf<MatrixUser?>(null) }
|
||||
val startDmActionState: MutableState<AsyncAction<RoomId>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||
val isBlocked: MutableState<AsyncData<Boolean>> = remember { mutableStateOf(AsyncData.Uninitialized) }
|
||||
LaunchedEffect(Unit) {
|
||||
client.ignoredUsersFlow
|
||||
.map { ignoredUsers -> userId in ignoredUsers }
|
||||
.distinctUntilChanged()
|
||||
.onEach { isBlocked.value = AsyncData.Success(it) }
|
||||
.launchIn(this)
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
userProfile = client.getProfile(userId).getOrNull()
|
||||
}
|
||||
|
||||
fun handleEvents(event: UserProfileEvents) {
|
||||
when (event) {
|
||||
is UserProfileEvents.BlockUser -> {
|
||||
if (event.needsConfirmation) {
|
||||
confirmationDialog = ConfirmationDialog.Block
|
||||
} else {
|
||||
confirmationDialog = null
|
||||
userProfilePresenterHelper.blockUser(coroutineScope, isBlocked)
|
||||
}
|
||||
}
|
||||
is UserProfileEvents.UnblockUser -> {
|
||||
if (event.needsConfirmation) {
|
||||
confirmationDialog = ConfirmationDialog.Unblock
|
||||
} else {
|
||||
confirmationDialog = null
|
||||
userProfilePresenterHelper.unblockUser(coroutineScope, isBlocked)
|
||||
}
|
||||
}
|
||||
UserProfileEvents.ClearConfirmationDialog -> confirmationDialog = null
|
||||
UserProfileEvents.ClearBlockUserError -> {
|
||||
isBlocked.value = AsyncData.Success(isBlocked.value.dataOrNull().orFalse())
|
||||
}
|
||||
UserProfileEvents.StartDM -> {
|
||||
coroutineScope.launch {
|
||||
startDMAction.execute(userId, startDmActionState)
|
||||
}
|
||||
}
|
||||
UserProfileEvents.ClearStartDMState -> {
|
||||
startDmActionState.value = AsyncAction.Uninitialized
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return UserProfileState(
|
||||
userId = userId,
|
||||
userName = userProfile?.displayName,
|
||||
avatarUrl = userProfile?.avatarUrl,
|
||||
isBlocked = isBlocked.value,
|
||||
startDmActionState = startDmActionState.value,
|
||||
displayConfirmationDialog = confirmationDialog,
|
||||
isCurrentUser = client.isMe(userId),
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.userprofile.impl
|
||||
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.ReceiveTurbine
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.createroom.api.StartDMAction
|
||||
import io.element.android.features.createroom.test.FakeStartDMAction
|
||||
import io.element.android.features.userprofile.impl.root.UserProfilePresenter
|
||||
import io.element.android.features.userprofile.shared.UserProfileEvents
|
||||
import io.element.android.features.userprofile.shared.UserProfileState
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_THROWABLE
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
class UserProfilePresenterTests {
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
fun `present - returns the user profile data`() = runTest {
|
||||
val matrixUser = aMatrixUser(A_USER_ID.value, "Alice", "anAvatarUrl")
|
||||
val client = FakeMatrixClient().apply {
|
||||
givenGetProfileResult(A_USER_ID, Result.success(matrixUser))
|
||||
}
|
||||
val presenter = createUserProfilePresenter(
|
||||
client = client,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
assertThat(initialState.userId).isEqualTo(matrixUser.userId)
|
||||
assertThat(initialState.userName).isEqualTo(matrixUser.displayName)
|
||||
assertThat(initialState.avatarUrl).isEqualTo(matrixUser.avatarUrl)
|
||||
assertThat(initialState.isBlocked).isEqualTo(AsyncData.Success(false))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - returns empty data in case of failure`() = runTest {
|
||||
val client = FakeMatrixClient().apply {
|
||||
givenGetProfileResult(A_USER_ID, Result.failure(AN_EXCEPTION))
|
||||
}
|
||||
val presenter = createUserProfilePresenter(
|
||||
client = client,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
assertThat(initialState.userId).isEqualTo(A_USER_ID)
|
||||
assertThat(initialState.userName).isNull()
|
||||
assertThat(initialState.avatarUrl).isNull()
|
||||
assertThat(initialState.isBlocked).isEqualTo(AsyncData.Success(false))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - BlockUser needing confirmation displays confirmation dialog`() = runTest {
|
||||
val presenter = createUserProfilePresenter()
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
initialState.eventSink(UserProfileEvents.BlockUser(needsConfirmation = true))
|
||||
|
||||
val dialogState = awaitItem()
|
||||
assertThat(dialogState.displayConfirmationDialog).isEqualTo(UserProfileState.ConfirmationDialog.Block)
|
||||
|
||||
dialogState.eventSink(UserProfileEvents.ClearConfirmationDialog)
|
||||
assertThat(awaitItem().displayConfirmationDialog).isNull()
|
||||
|
||||
ensureAllEventsConsumed()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - BlockUser and UnblockUser without confirmation change the 'blocked' state`() = runTest {
|
||||
val client = FakeMatrixClient()
|
||||
val presenter = createUserProfilePresenter(
|
||||
client = client,
|
||||
userId = A_USER_ID
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
initialState.eventSink(UserProfileEvents.BlockUser(needsConfirmation = false))
|
||||
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
|
||||
client.emitIgnoreUserList(listOf(A_USER_ID))
|
||||
assertThat(awaitItem().isBlocked.dataOrNull()).isTrue()
|
||||
|
||||
initialState.eventSink(UserProfileEvents.UnblockUser(needsConfirmation = false))
|
||||
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
|
||||
client.emitIgnoreUserList(listOf())
|
||||
assertThat(awaitItem().isBlocked.dataOrNull()).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - BlockUser with error`() = runTest {
|
||||
val matrixClient = FakeMatrixClient()
|
||||
matrixClient.givenIgnoreUserResult(Result.failure(A_THROWABLE))
|
||||
val presenter = createUserProfilePresenter(client = matrixClient)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
initialState.eventSink(UserProfileEvents.BlockUser(needsConfirmation = false))
|
||||
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
|
||||
val errorState = awaitItem()
|
||||
assertThat(errorState.isBlocked.errorOrNull()).isEqualTo(A_THROWABLE)
|
||||
// Clear error
|
||||
initialState.eventSink(UserProfileEvents.ClearBlockUserError)
|
||||
assertThat(awaitItem().isBlocked).isEqualTo(AsyncData.Success(false))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - UnblockUser with error`() = runTest {
|
||||
val matrixClient = FakeMatrixClient()
|
||||
matrixClient.givenUnignoreUserResult(Result.failure(A_THROWABLE))
|
||||
val presenter = createUserProfilePresenter(client = matrixClient)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
initialState.eventSink(UserProfileEvents.UnblockUser(needsConfirmation = false))
|
||||
assertThat(awaitItem().isBlocked.isLoading()).isTrue()
|
||||
val errorState = awaitItem()
|
||||
assertThat(errorState.isBlocked.errorOrNull()).isEqualTo(A_THROWABLE)
|
||||
// Clear error
|
||||
initialState.eventSink(UserProfileEvents.ClearBlockUserError)
|
||||
assertThat(awaitItem().isBlocked).isEqualTo(AsyncData.Success(true))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - UnblockUser needing confirmation displays confirmation dialog`() = runTest {
|
||||
val presenter = createUserProfilePresenter()
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
initialState.eventSink(UserProfileEvents.UnblockUser(needsConfirmation = true))
|
||||
|
||||
val dialogState = awaitItem()
|
||||
assertThat(dialogState.displayConfirmationDialog).isEqualTo(UserProfileState.ConfirmationDialog.Unblock)
|
||||
|
||||
dialogState.eventSink(UserProfileEvents.ClearConfirmationDialog)
|
||||
assertThat(awaitItem().displayConfirmationDialog).isNull()
|
||||
|
||||
ensureAllEventsConsumed()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - start DM action complete scenario`() = runTest {
|
||||
val startDMAction = FakeStartDMAction()
|
||||
val presenter = createUserProfilePresenter(startDMAction = startDMAction)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
assertThat(initialState.startDmActionState).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||
val startDMSuccessResult = AsyncAction.Success(A_ROOM_ID)
|
||||
val startDMFailureResult = AsyncAction.Failure(A_THROWABLE)
|
||||
|
||||
// Failure
|
||||
startDMAction.givenExecuteResult(startDMFailureResult)
|
||||
initialState.eventSink(UserProfileEvents.StartDM)
|
||||
assertThat(awaitItem().startDmActionState).isInstanceOf(AsyncAction.Loading::class.java)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.startDmActionState).isEqualTo(startDMFailureResult)
|
||||
state.eventSink(UserProfileEvents.ClearStartDMState)
|
||||
}
|
||||
|
||||
// Success
|
||||
startDMAction.givenExecuteResult(startDMSuccessResult)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.startDmActionState).isEqualTo(AsyncAction.Uninitialized)
|
||||
state.eventSink(UserProfileEvents.StartDM)
|
||||
}
|
||||
assertThat(awaitItem().startDmActionState).isInstanceOf(AsyncAction.Loading::class.java)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.startDmActionState).isEqualTo(startDMSuccessResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun <T> ReceiveTurbine<T>.awaitFirstItem(): T {
|
||||
skipItems(1)
|
||||
return awaitItem()
|
||||
}
|
||||
|
||||
private fun createUserProfilePresenter(
|
||||
client: MatrixClient = FakeMatrixClient(),
|
||||
userId: UserId = UserId("@alice:server.org"),
|
||||
startDMAction: StartDMAction = FakeStartDMAction()
|
||||
): UserProfilePresenter {
|
||||
return UserProfilePresenter(
|
||||
userId = userId,
|
||||
client = client,
|
||||
startDMAction = startDMAction
|
||||
)
|
||||
}
|
||||
}
|
||||
71
features/userprofile/shared/build.gradle.kts
Normal file
71
features/userprofile/shared/build.gradle.kts
Normal file
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
alias(libs.plugins.anvil)
|
||||
alias(libs.plugins.ksp)
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.userprofile.shared"
|
||||
testOptions {
|
||||
unitTests {
|
||||
isIncludeAndroidResources = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
anvil {
|
||||
generateDaggerFactories.set(true)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
anvil(projects.anvilcodegen)
|
||||
implementation(projects.anvilannotations)
|
||||
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.matrixui)
|
||||
implementation(projects.libraries.designsystem)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
implementation(projects.libraries.androidutils)
|
||||
implementation(projects.libraries.mediaviewer.api)
|
||||
implementation(projects.libraries.featureflag.api)
|
||||
implementation(projects.libraries.permissions.api)
|
||||
implementation(projects.libraries.preferences.api)
|
||||
implementation(projects.libraries.testtags)
|
||||
api(projects.features.userprofile.api)
|
||||
api(projects.services.apperror.api)
|
||||
implementation(libs.coil.compose)
|
||||
implementation(projects.features.createroom.api)
|
||||
implementation(projects.services.analytics.api)
|
||||
|
||||
testImplementation(libs.test.junit)
|
||||
testImplementation(libs.coroutines.test)
|
||||
testImplementation(libs.molecule.runtime)
|
||||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.test.turbine)
|
||||
testImplementation(libs.test.robolectric)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.tests.testutils)
|
||||
testImplementation(libs.androidx.compose.ui.test.junit)
|
||||
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
||||
|
||||
ksp(libs.showkase.processor)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,13 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomdetails.impl.members.details
|
||||
package io.element.android.features.userprofile.shared
|
||||
|
||||
sealed interface RoomMemberDetailsEvents {
|
||||
data object StartDM : RoomMemberDetailsEvents
|
||||
data object ClearStartDMState : RoomMemberDetailsEvents
|
||||
data class BlockUser(val needsConfirmation: Boolean = false) : RoomMemberDetailsEvents
|
||||
data class UnblockUser(val needsConfirmation: Boolean = false) : RoomMemberDetailsEvents
|
||||
data object ClearBlockUserError : RoomMemberDetailsEvents
|
||||
data object ClearConfirmationDialog : RoomMemberDetailsEvents
|
||||
sealed interface UserProfileEvents {
|
||||
data object StartDM : UserProfileEvents
|
||||
data object ClearStartDMState : UserProfileEvents
|
||||
data class BlockUser(val needsConfirmation: Boolean = false) : UserProfileEvents
|
||||
data class UnblockUser(val needsConfirmation: Boolean = false) : UserProfileEvents
|
||||
data object ClearBlockUserError : UserProfileEvents
|
||||
data object ClearConfirmationDialog : UserProfileEvents
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomdetails.impl.members.details
|
||||
package io.element.android.features.userprofile.shared
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -37,13 +37,14 @@ import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.testtags.TestTags
|
||||
import io.element.android.libraries.testtags.testTag
|
||||
|
||||
@Composable
|
||||
fun RoomMemberHeaderSection(
|
||||
fun UserProfileHeaderSection(
|
||||
avatarUrl: String?,
|
||||
userId: String,
|
||||
userId: UserId,
|
||||
userName: String?,
|
||||
openAvatarPreview: (url: String) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
@@ -51,7 +52,7 @@ fun RoomMemberHeaderSection(
|
||||
Column(modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Box(modifier = Modifier.size(70.dp)) {
|
||||
Avatar(
|
||||
avatarData = AvatarData(userId, userName, avatarUrl, AvatarSize.UserHeader),
|
||||
avatarData = AvatarData(userId.value, userName, avatarUrl, AvatarSize.UserHeader),
|
||||
modifier = Modifier
|
||||
.clickable(enabled = avatarUrl != null) { openAvatarPreview(avatarUrl!!) }
|
||||
.fillMaxSize()
|
||||
@@ -68,7 +69,7 @@ fun RoomMemberHeaderSection(
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
}
|
||||
Text(
|
||||
text = userId,
|
||||
text = userId.value,
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
color = MaterialTheme.colorScheme.secondary,
|
||||
modifier = Modifier
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomdetails.impl.members.details
|
||||
package io.element.android.features.userprofile.shared
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -27,7 +27,7 @@ import io.element.android.libraries.designsystem.components.button.MainActionBut
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@Composable
|
||||
fun RoomMemberMainActionsSection(onShareUser: () -> Unit, modifier: Modifier = Modifier) {
|
||||
fun UserProfileMainActionsSection(onShareUser: () -> Unit, modifier: Modifier = Modifier) {
|
||||
Row(modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
|
||||
MainActionButton(
|
||||
title = stringResource(CommonStrings.action_share),
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.userprofile.shared
|
||||
|
||||
import android.content.Context
|
||||
import io.element.android.libraries.androidutils.R
|
||||
import io.element.android.libraries.androidutils.system.startSharePlainTextIntent
|
||||
import io.element.android.libraries.architecture.NodeInputs
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import timber.log.Timber
|
||||
|
||||
class UserProfileNodeHelper(
|
||||
private val userId: UserId,
|
||||
) {
|
||||
interface Callback : NodeInputs {
|
||||
fun openAvatarPreview(username: String, avatarUrl: String)
|
||||
fun onStartDM(roomId: RoomId)
|
||||
}
|
||||
|
||||
fun onShareUser(
|
||||
context: Context,
|
||||
permalinkBuilder: PermalinkBuilder,
|
||||
) {
|
||||
val permalinkResult = permalinkBuilder.permalinkForUser(userId)
|
||||
permalinkResult.onSuccess { permalink ->
|
||||
context.startSharePlainTextIntent(
|
||||
activityResultLauncher = null,
|
||||
chooserTitle = context.getString(CommonStrings.action_share),
|
||||
text = permalink,
|
||||
noActivityFoundMessage = context.getString(R.string.error_no_compatible_app_found)
|
||||
)
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.userprofile.shared
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class UserProfilePresenterHelper(
|
||||
private val userId: UserId,
|
||||
private val client: MatrixClient,
|
||||
) {
|
||||
fun blockUser(
|
||||
scope: CoroutineScope,
|
||||
isBlockedState: MutableState<AsyncData<Boolean>>,
|
||||
) = scope.launch {
|
||||
isBlockedState.value = AsyncData.Loading(false)
|
||||
client.ignoreUser(userId)
|
||||
.onFailure {
|
||||
isBlockedState.value = AsyncData.Failure(it, false)
|
||||
}
|
||||
// Note: on success, ignoredUserList will be updated.
|
||||
}
|
||||
|
||||
fun unblockUser(
|
||||
scope: CoroutineScope,
|
||||
isBlockedState: MutableState<AsyncData<Boolean>>,
|
||||
) = scope.launch {
|
||||
isBlockedState.value = AsyncData.Loading(true)
|
||||
client.unignoreUser(userId)
|
||||
.onFailure {
|
||||
isBlockedState.value = AsyncData.Failure(it, true)
|
||||
}
|
||||
// Note: on success, ignoredUserList will be updated.
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,21 +14,22 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomdetails.impl.members.details
|
||||
package io.element.android.features.userprofile.shared
|
||||
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
|
||||
data class RoomMemberDetailsState(
|
||||
val userId: String,
|
||||
data class UserProfileState(
|
||||
val userId: UserId,
|
||||
val userName: String?,
|
||||
val avatarUrl: String?,
|
||||
val isBlocked: AsyncData<Boolean>,
|
||||
val startDmActionState: AsyncAction<RoomId>,
|
||||
val displayConfirmationDialog: ConfirmationDialog?,
|
||||
val isCurrentUser: Boolean,
|
||||
val eventSink: (RoomMemberDetailsEvents) -> Unit
|
||||
val eventSink: (UserProfileEvents) -> Unit
|
||||
) {
|
||||
enum class ConfirmationDialog {
|
||||
Block,
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,37 +14,38 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomdetails.impl.members.details
|
||||
package io.element.android.features.userprofile.shared
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
|
||||
open class RoomMemberDetailsStateProvider : PreviewParameterProvider<RoomMemberDetailsState> {
|
||||
override val values: Sequence<RoomMemberDetailsState>
|
||||
open class UserProfileStateProvider : PreviewParameterProvider<UserProfileState> {
|
||||
override val values: Sequence<UserProfileState>
|
||||
get() = sequenceOf(
|
||||
aRoomMemberDetailsState(),
|
||||
aRoomMemberDetailsState(userName = null),
|
||||
aRoomMemberDetailsState(isBlocked = AsyncData.Success(true)),
|
||||
aRoomMemberDetailsState(displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Block),
|
||||
aRoomMemberDetailsState(displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Unblock),
|
||||
aRoomMemberDetailsState(isBlocked = AsyncData.Loading(true)),
|
||||
aRoomMemberDetailsState(startDmActionState = AsyncAction.Loading),
|
||||
aUserProfileState(),
|
||||
aUserProfileState(userName = null),
|
||||
aUserProfileState(isBlocked = AsyncData.Success(true)),
|
||||
aUserProfileState(displayConfirmationDialog = UserProfileState.ConfirmationDialog.Block),
|
||||
aUserProfileState(displayConfirmationDialog = UserProfileState.ConfirmationDialog.Unblock),
|
||||
aUserProfileState(isBlocked = AsyncData.Loading(true)),
|
||||
aUserProfileState(startDmActionState = AsyncAction.Loading),
|
||||
// Add other states here
|
||||
)
|
||||
}
|
||||
|
||||
fun aRoomMemberDetailsState(
|
||||
userId: String = "@daniel:domain.com",
|
||||
fun aUserProfileState(
|
||||
userId: UserId = UserId("@daniel:domain.com"),
|
||||
userName: String? = "Daniel",
|
||||
avatarUrl: String? = null,
|
||||
isBlocked: AsyncData<Boolean> = AsyncData.Success(false),
|
||||
startDmActionState: AsyncAction<RoomId> = AsyncAction.Uninitialized,
|
||||
displayConfirmationDialog: RoomMemberDetailsState.ConfirmationDialog? = null,
|
||||
displayConfirmationDialog: UserProfileState.ConfirmationDialog? = null,
|
||||
isCurrentUser: Boolean = false,
|
||||
eventSink: (RoomMemberDetailsEvents) -> Unit = {},
|
||||
) = RoomMemberDetailsState(
|
||||
eventSink: (UserProfileEvents) -> Unit = {},
|
||||
) = UserProfileState(
|
||||
userId = userId,
|
||||
userName = userName,
|
||||
avatarUrl = avatarUrl,
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomdetails.impl.members.details
|
||||
package io.element.android.features.userprofile.shared
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
@@ -30,17 +30,14 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.roomdetails.impl.R
|
||||
import io.element.android.features.roomdetails.impl.blockuser.BlockUserDialogs
|
||||
import io.element.android.features.roomdetails.impl.blockuser.BlockUserSection
|
||||
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
|
||||
import io.element.android.features.userprofile.shared.blockuser.BlockUserDialogs
|
||||
import io.element.android.features.userprofile.shared.blockuser.BlockUserSection
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncActionViewDefaults
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.list.ListItemContent
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||
import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.IconSource
|
||||
import io.element.android.libraries.designsystem.theme.components.ListItem
|
||||
import io.element.android.libraries.designsystem.theme.components.ListItemStyle
|
||||
@@ -52,8 +49,8 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun RoomMemberDetailsView(
|
||||
state: RoomMemberDetailsState,
|
||||
fun UserProfileView(
|
||||
state: UserProfileState,
|
||||
onShareUser: () -> Unit,
|
||||
onDMStarted: (RoomId) -> Unit,
|
||||
goBack: () -> Unit,
|
||||
@@ -72,21 +69,21 @@ fun RoomMemberDetailsView(
|
||||
.consumeWindowInsets(padding)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
RoomMemberHeaderSection(
|
||||
UserProfileHeaderSection(
|
||||
avatarUrl = state.avatarUrl,
|
||||
userId = state.userId,
|
||||
userName = state.userName,
|
||||
openAvatarPreview = { avatarUrl ->
|
||||
openAvatarPreview(state.userName ?: state.userId, avatarUrl)
|
||||
openAvatarPreview(state.userName ?: state.userId.value, avatarUrl)
|
||||
},
|
||||
)
|
||||
|
||||
RoomMemberMainActionsSection(onShareUser = onShareUser)
|
||||
UserProfileMainActionsSection(onShareUser = onShareUser)
|
||||
|
||||
Spacer(modifier = Modifier.height(26.dp))
|
||||
|
||||
if (!state.isCurrentUser) {
|
||||
StartDMSection(onStartDMClicked = { state.eventSink(RoomMemberDetailsEvents.StartDM) })
|
||||
StartDMSection(onStartDMClicked = { state.eventSink(UserProfileEvents.StartDM) })
|
||||
BlockUserSection(state)
|
||||
BlockUserDialogs(state)
|
||||
}
|
||||
@@ -99,8 +96,8 @@ fun RoomMemberDetailsView(
|
||||
},
|
||||
onSuccess = onDMStarted,
|
||||
errorMessage = { stringResource(R.string.screen_start_chat_error_starting_chat) },
|
||||
onRetry = { state.eventSink(RoomMemberDetailsEvents.StartDM) },
|
||||
onErrorDismiss = { state.eventSink(RoomMemberDetailsEvents.ClearStartDMState) },
|
||||
onRetry = { state.eventSink(UserProfileEvents.StartDM) },
|
||||
onErrorDismiss = { state.eventSink(UserProfileEvents.ClearStartDMState) },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -118,20 +115,12 @@ private fun StartDMSection(
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewWithLargeHeight
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun RoomMemberDetailsViewLightPreview(@PreviewParameter(RoomMemberDetailsStateProvider::class) state: RoomMemberDetailsState) =
|
||||
ElementPreviewLight { ContentToPreview(state) }
|
||||
|
||||
@PreviewWithLargeHeight
|
||||
@Composable
|
||||
internal fun RoomMemberDetailsViewDarkPreview(@PreviewParameter(RoomMemberDetailsStateProvider::class) state: RoomMemberDetailsState) =
|
||||
ElementPreviewDark { ContentToPreview(state) }
|
||||
|
||||
@ExcludeFromCoverage
|
||||
@Composable
|
||||
private fun ContentToPreview(state: RoomMemberDetailsState) {
|
||||
RoomMemberDetailsView(
|
||||
internal fun UserProfileViewPreview(
|
||||
@PreviewParameter(UserProfileStateProvider::class) state: UserProfileState
|
||||
) = ElementPreview {
|
||||
UserProfileView(
|
||||
state = state,
|
||||
onShareUser = {},
|
||||
goBack = {},
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,18 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomdetails.impl.members.details.avatar
|
||||
package io.element.android.features.userprofile.shared.avatar
|
||||
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.mediaviewer.api.viewer.MediaViewerNode
|
||||
import io.element.android.libraries.mediaviewer.api.viewer.MediaViewerPresenter
|
||||
|
||||
@ContributesNode(RoomScope::class)
|
||||
@ContributesNode(SessionScope::class)
|
||||
class AvatarPreviewNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,41 +14,41 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomdetails.impl.blockuser
|
||||
package io.element.android.features.userprofile.shared.blockuser
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import io.element.android.features.roomdetails.impl.R
|
||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsEvents
|
||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState
|
||||
import io.element.android.features.userprofile.shared.R
|
||||
import io.element.android.features.userprofile.shared.UserProfileEvents
|
||||
import io.element.android.features.userprofile.shared.UserProfileState
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||
|
||||
@Composable
|
||||
fun BlockUserDialogs(state: RoomMemberDetailsState) {
|
||||
fun BlockUserDialogs(state: UserProfileState) {
|
||||
when (state.displayConfirmationDialog) {
|
||||
null -> Unit
|
||||
RoomMemberDetailsState.ConfirmationDialog.Block -> {
|
||||
UserProfileState.ConfirmationDialog.Block -> {
|
||||
BlockConfirmationDialog(
|
||||
onBlockAction = {
|
||||
state.eventSink(
|
||||
RoomMemberDetailsEvents.BlockUser(
|
||||
UserProfileEvents.BlockUser(
|
||||
needsConfirmation = false
|
||||
)
|
||||
)
|
||||
},
|
||||
onDismiss = { state.eventSink(RoomMemberDetailsEvents.ClearConfirmationDialog) }
|
||||
onDismiss = { state.eventSink(UserProfileEvents.ClearConfirmationDialog) }
|
||||
)
|
||||
}
|
||||
RoomMemberDetailsState.ConfirmationDialog.Unblock -> {
|
||||
UserProfileState.ConfirmationDialog.Unblock -> {
|
||||
UnblockConfirmationDialog(
|
||||
onUnblockAction = {
|
||||
state.eventSink(
|
||||
RoomMemberDetailsEvents.UnblockUser(
|
||||
UserProfileEvents.UnblockUser(
|
||||
needsConfirmation = false
|
||||
)
|
||||
)
|
||||
},
|
||||
onDismiss = { state.eventSink(RoomMemberDetailsEvents.ClearConfirmationDialog) }
|
||||
onDismiss = { state.eventSink(UserProfileEvents.ClearConfirmationDialog) }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomdetails.impl.blockuser
|
||||
package io.element.android.features.userprofile.shared.blockuser
|
||||
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.progressSemantics
|
||||
@@ -23,9 +23,9 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.roomdetails.impl.R
|
||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsEvents
|
||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState
|
||||
import io.element.android.features.userprofile.shared.R
|
||||
import io.element.android.features.userprofile.shared.UserProfileEvents
|
||||
import io.element.android.features.userprofile.shared.UserProfileState
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.core.bool.orFalse
|
||||
import io.element.android.libraries.designsystem.components.dialogs.RetryDialog
|
||||
@@ -39,8 +39,14 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@Composable
|
||||
internal fun BlockUserSection(state: RoomMemberDetailsState) {
|
||||
PreferenceCategory(showDivider = false) {
|
||||
fun BlockUserSection(
|
||||
state: UserProfileState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
PreferenceCategory(
|
||||
modifier = modifier,
|
||||
showDivider = false,
|
||||
) {
|
||||
when (state.isBlocked) {
|
||||
is AsyncData.Failure -> PreferenceBlockUser(isBlocked = state.isBlocked.prevData, isLoading = false, eventSink = state.eventSink)
|
||||
is AsyncData.Loading -> PreferenceBlockUser(isBlocked = state.isBlocked.prevData, isLoading = true, eventSink = state.eventSink)
|
||||
@@ -51,13 +57,13 @@ internal fun BlockUserSection(state: RoomMemberDetailsState) {
|
||||
if (state.isBlocked is AsyncData.Failure) {
|
||||
RetryDialog(
|
||||
content = stringResource(CommonStrings.error_unknown),
|
||||
onDismiss = { state.eventSink(RoomMemberDetailsEvents.ClearBlockUserError) },
|
||||
onDismiss = { state.eventSink(UserProfileEvents.ClearBlockUserError) },
|
||||
onRetry = {
|
||||
val event = when (state.isBlocked.prevData) {
|
||||
true -> RoomMemberDetailsEvents.UnblockUser(needsConfirmation = false)
|
||||
false -> RoomMemberDetailsEvents.BlockUser(needsConfirmation = false)
|
||||
true -> UserProfileEvents.UnblockUser(needsConfirmation = false)
|
||||
false -> UserProfileEvents.BlockUser(needsConfirmation = false)
|
||||
// null case Should not happen
|
||||
null -> RoomMemberDetailsEvents.ClearBlockUserError
|
||||
null -> UserProfileEvents.ClearBlockUserError
|
||||
}
|
||||
state.eventSink(event)
|
||||
},
|
||||
@@ -69,7 +75,7 @@ internal fun BlockUserSection(state: RoomMemberDetailsState) {
|
||||
private fun PreferenceBlockUser(
|
||||
isBlocked: Boolean?,
|
||||
isLoading: Boolean,
|
||||
eventSink: (RoomMemberDetailsEvents) -> Unit,
|
||||
eventSink: (UserProfileEvents) -> Unit,
|
||||
) {
|
||||
val loadingCurrentValue = @Composable {
|
||||
CircularProgressIndicator(
|
||||
@@ -83,7 +89,7 @@ private fun PreferenceBlockUser(
|
||||
ListItem(
|
||||
headlineContent = { Text(stringResource(R.string.screen_dm_details_unblock_user)) },
|
||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Block())),
|
||||
onClick = { if (!isLoading) eventSink(RoomMemberDetailsEvents.UnblockUser(needsConfirmation = true)) },
|
||||
onClick = { if (!isLoading) eventSink(UserProfileEvents.UnblockUser(needsConfirmation = true)) },
|
||||
trailingContent = if (isLoading) ListItemContent.Custom(loadingCurrentValue) else null,
|
||||
style = ListItemStyle.Primary,
|
||||
)
|
||||
@@ -92,7 +98,7 @@ private fun PreferenceBlockUser(
|
||||
headlineContent = { Text(stringResource(R.string.screen_dm_details_block_user)) },
|
||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Block())),
|
||||
style = ListItemStyle.Destructive,
|
||||
onClick = { if (!isLoading) eventSink(RoomMemberDetailsEvents.BlockUser(needsConfirmation = true)) },
|
||||
onClick = { if (!isLoading) eventSink(UserProfileEvents.BlockUser(needsConfirmation = true)) },
|
||||
trailingContent = if (isLoading) ListItemContent.Custom(loadingCurrentValue) else null,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Заблакіраваць"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"Заблакіраваныя карыстальнікі не змогуць адпраўляць вам паведамленні, і ўсе іх паведамленні будуць схаваны. Вы можаце разблакіраваць іх у любы час."</string>
|
||||
<string name="screen_dm_details_block_user">"Заблакіраваць карыстальніка"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Разблакіраваць"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"Вы зноў зможаце ўбачыць усе паведамленні."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Разблакіраваць карыстальніка"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Пры спробе пачаць чат адбылася памылка"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Блокиране"</string>
|
||||
<string name="screen_dm_details_block_user">"Блокиране на потребителя"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Отблокиране"</string>
|
||||
<string name="screen_dm_details_unblock_user">"Отблокиране на потребителя"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Zablokovat"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"Blokovaní uživatelé vám nebudou moci posílat zprávy a všechny jejich zprávy budou skryty. Můžete je kdykoli odblokovat."</string>
|
||||
<string name="screen_dm_details_block_user">"Zablokovat uživatele"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Odblokovat"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"Znovu uvidíte všechny zprávy od nich."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Odblokovat uživatele"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Při pokusu o zahájení chatu došlo k chybě"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Blockieren"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"Blockierte Benutzer können Dir keine Nachrichten senden und alle ihre alten Nachrichten werden ausgeblendet. Die Blockierung kann jederzeit aufgehoben werden."</string>
|
||||
<string name="screen_dm_details_block_user">"Benutzer blockieren"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Blockierung aufheben"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"Der Nutzer kann dir wieder Nachrichten senden & alle Nachrichten des Nutzers werden wieder angezeigt."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Blockierung aufheben"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Beim Versuch, einen Chat zu starten, ist ein Fehler aufgetreten"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Bloquear"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"Los usuarios bloqueados no podrán enviarte mensajes y todos sus mensajes se ocultarán. Puedes desbloquearlos cuando quieras."</string>
|
||||
<string name="screen_dm_details_block_user">"Bloquear usuario"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Desbloquear"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"Podrás ver todos sus mensajes de nuevo."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Desbloquear usuario"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Se ha producido un error al intentar iniciar un chat"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Bloquer"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"Les utilisateurs bloqués ne pourront pas vous envoyer de messages et tous leurs messages seront masqués. Vous pouvez les débloquer à tout moment."</string>
|
||||
<string name="screen_dm_details_block_user">"Bloquer l’utilisateur"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Débloquer"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"Vous pourrez à nouveau voir tous ses messages."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Débloquer l’utilisateur"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Une erreur s’est produite lors de la tentative de création de la discussion"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Letiltás"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"A letiltott felhasználók nem fognak tudni üzeneteket küldeni, és az összes üzenetük rejtve lesz. Bármikor feloldhatja a letiltásukat."</string>
|
||||
<string name="screen_dm_details_block_user">"Felhasználó letiltása"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Letiltás feloldása"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"Újra láthatja az összes üzenetét."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Felhasználó kitiltásának feloldása"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Hiba történt a csevegés indításakor"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Blocca"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"Gli utenti bloccati non saranno in grado di inviarti messaggi e tutti quelli già ricevuti saranno nascosti. Puoi sbloccarli in qualsiasi momento."</string>
|
||||
<string name="screen_dm_details_block_user">"Blocca utente"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Sblocca"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"Potrai vedere di nuovo tutti i suoi messaggi."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Sblocca utente"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Si è verificato un errore durante il tentativo di avviare una chat"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Blocați"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"Utilizatorii blocați nu vă vor putea trimite mesaje și toate mesajele lor vor fi ascunse. Puteți anula această acțiune oricând."</string>
|
||||
<string name="screen_dm_details_block_user">"Blocați utilizatorul"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Deblocați"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"La deblocarea utilizatorului, veți putea vedea din nou toate mesajele de la acesta."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Deblocați utilizatorul"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"A apărut o eroare la încercarea începerii conversației"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Заблокировать"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"Заблокированные пользователи не смогут отправлять вам сообщения, а все их сообщения будут скрыты. Вы можете разблокировать их в любое время."</string>
|
||||
<string name="screen_dm_details_block_user">"Заблокировать пользователя"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Разблокировать"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"Вы снова сможете увидеть все сообщения."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Разблокировать пользователя"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Произошла ошибка при попытке открытия комнаты"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Zablokovať"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"Blokovaní používatelia vám nebudú môcť posielať správy a všetky ich správy budú skryté. Môžete ich kedykoľvek odblokovať."</string>
|
||||
<string name="screen_dm_details_block_user">"Zablokovať používateľa"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Odblokovať"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"Všetky správy od nich budete môcť opäť vidieť."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Odblokovať používateľa"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Pri pokuse o spustenie konverzácie sa vyskytla chyba"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Blockera"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"Blockerade användare kommer inte att kunna skicka meddelanden till dig och alla deras meddelanden kommer att döljas. Du kan avblockera dem när som helst."</string>
|
||||
<string name="screen_dm_details_block_user">"Blockera användare"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Avblockera"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"Du kommer att kunna se alla meddelanden från dem igen."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Avblockera användare"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Ett fel uppstod när du försökte starta en chatt"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"Заблокувати"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"Заблоковані користувачі не зможуть надсилати Вам повідомлення, і всі їхні повідомлення будуть приховані. Ви можете розблокувати їх у будь-який час."</string>
|
||||
<string name="screen_dm_details_block_user">"Заблокувати користувача"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Розблокувати"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"Ви знову зможете бачити всі повідомлення від них."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Розблокувати користувача"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"Під час спроби почати чат сталася помилка"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_dm_details_block_alert_action">"封鎖"</string>
|
||||
<string name="screen_dm_details_block_user">"封鎖使用者"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"解除封鎖"</string>
|
||||
<string name="screen_dm_details_unblock_user">"解除封鎖使用者"</string>
|
||||
</resources>
|
||||
10
features/userprofile/shared/src/main/res/values/localazy.xml
Normal file
10
features/userprofile/shared/src/main/res/values/localazy.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">
|
||||
<string name="screen_dm_details_block_alert_action">"Block"</string>
|
||||
<string name="screen_dm_details_block_alert_description">"Blocked users won\'t be able to send you messages and all their messages will be hidden. You can unblock them anytime."</string>
|
||||
<string name="screen_dm_details_block_user">"Block user"</string>
|
||||
<string name="screen_dm_details_unblock_alert_action">"Unblock"</string>
|
||||
<string name="screen_dm_details_unblock_alert_description">"You\'ll be able to see all messages from them again."</string>
|
||||
<string name="screen_dm_details_unblock_user">"Unblock user"</string>
|
||||
<string name="screen_start_chat_error_starting_chat">"An error occurred when trying to start a chat"</string>
|
||||
</resources>
|
||||
@@ -14,15 +14,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomdetails.impl.blockuser
|
||||
package io.element.android.features.userprofile.shared.blockuser
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.features.roomdetails.impl.R
|
||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsEvents
|
||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState
|
||||
import io.element.android.features.roomdetails.impl.members.details.aRoomMemberDetailsState
|
||||
import io.element.android.features.userprofile.shared.R
|
||||
import io.element.android.features.userprofile.shared.UserProfileEvents
|
||||
import io.element.android.features.userprofile.shared.UserProfileState
|
||||
import io.element.android.features.userprofile.shared.aUserProfileState
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.clickOn
|
||||
@@ -36,61 +36,61 @@ class BlockUserDialogsTest {
|
||||
|
||||
@Test
|
||||
fun `confirm block user emit expected Event`() {
|
||||
val eventsRecorder = EventsRecorder<RoomMemberDetailsEvents>()
|
||||
val eventsRecorder = EventsRecorder<UserProfileEvents>()
|
||||
rule.setContent {
|
||||
BlockUserDialogs(
|
||||
state = aRoomMemberDetailsState(
|
||||
displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Block,
|
||||
state = aUserProfileState(
|
||||
displayConfirmationDialog = UserProfileState.ConfirmationDialog.Block,
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
)
|
||||
}
|
||||
rule.clickOn(R.string.screen_dm_details_block_alert_action)
|
||||
eventsRecorder.assertSingle(RoomMemberDetailsEvents.BlockUser(false))
|
||||
eventsRecorder.assertSingle(UserProfileEvents.BlockUser(false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cancel block user emit expected Event`() {
|
||||
val eventsRecorder = EventsRecorder<RoomMemberDetailsEvents>()
|
||||
val eventsRecorder = EventsRecorder<UserProfileEvents>()
|
||||
rule.setContent {
|
||||
BlockUserDialogs(
|
||||
state = aRoomMemberDetailsState(
|
||||
displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Block,
|
||||
state = aUserProfileState(
|
||||
displayConfirmationDialog = UserProfileState.ConfirmationDialog.Block,
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
)
|
||||
}
|
||||
rule.clickOn(CommonStrings.action_cancel)
|
||||
eventsRecorder.assertSingle(RoomMemberDetailsEvents.ClearConfirmationDialog)
|
||||
eventsRecorder.assertSingle(UserProfileEvents.ClearConfirmationDialog)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `confirm unblock user emit expected Event`() {
|
||||
val eventsRecorder = EventsRecorder<RoomMemberDetailsEvents>()
|
||||
val eventsRecorder = EventsRecorder<UserProfileEvents>()
|
||||
rule.setContent {
|
||||
BlockUserDialogs(
|
||||
state = aRoomMemberDetailsState(
|
||||
displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Unblock,
|
||||
state = aUserProfileState(
|
||||
displayConfirmationDialog = UserProfileState.ConfirmationDialog.Unblock,
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
)
|
||||
}
|
||||
rule.clickOn(R.string.screen_dm_details_unblock_alert_action)
|
||||
eventsRecorder.assertSingle(RoomMemberDetailsEvents.UnblockUser(false))
|
||||
eventsRecorder.assertSingle(UserProfileEvents.UnblockUser(false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cancel unblock user emit expected Event`() {
|
||||
val eventsRecorder = EventsRecorder<RoomMemberDetailsEvents>()
|
||||
val eventsRecorder = EventsRecorder<UserProfileEvents>()
|
||||
rule.setContent {
|
||||
BlockUserDialogs(
|
||||
state = aRoomMemberDetailsState(
|
||||
displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Unblock,
|
||||
state = aUserProfileState(
|
||||
displayConfirmationDialog = UserProfileState.ConfirmationDialog.Unblock,
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
)
|
||||
}
|
||||
rule.clickOn(CommonStrings.action_cancel)
|
||||
eventsRecorder.assertSingle(RoomMemberDetailsEvents.ClearConfirmationDialog)
|
||||
eventsRecorder.assertSingle(UserProfileEvents.ClearConfirmationDialog)
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
<string name="screen_session_verification_positive_button_canceled">"Försök att verifiera igen"</string>
|
||||
<string name="screen_session_verification_positive_button_initial">"Jag är redo"</string>
|
||||
<string name="screen_session_verification_positive_button_verifying_ongoing">"Väntar på att matcha"</string>
|
||||
<string name="screen_session_verification_ready_subtitle">"Jämför en unik uppsättning emojis."</string>
|
||||
<string name="screen_session_verification_request_accepted_subtitle">"Jämför de unika emojierna och se till att de visas i samma ordning."</string>
|
||||
<string name="screen_session_verification_they_dont_match">"De matchar inte"</string>
|
||||
<string name="screen_session_verification_they_match">"De matchar"</string>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_identity_confirmed_title">"裝置已認證"</string>
|
||||
<string name="screen_identity_use_another_device">"使用另一個裝置"</string>
|
||||
<string name="screen_identity_confirmation_create_new_recovery_key">"建立新的復原金鑰"</string>
|
||||
<string name="screen_identity_confirmation_subtitle">"驗證這部裝置以設定安全通訊。"</string>
|
||||
<string name="screen_identity_confirmation_title">"確認這是你本人"</string>
|
||||
<string name="screen_identity_confirmed_subtitle">"您可以安全地讀取和發送訊息了,與您聊天的人也可以信任這部裝置。"</string>
|
||||
<string name="screen_identity_confirmed_title">"裝置已驗證"</string>
|
||||
<string name="screen_identity_use_another_device">"使用另一部裝置"</string>
|
||||
<string name="screen_identity_waiting_on_other_device">"正在等待其他裝置……"</string>
|
||||
<string name="screen_session_verification_cancelled_subtitle">"似乎出了一點問題。有可能是因為等候逾時,或是請求被拒絕。"</string>
|
||||
<string name="screen_session_verification_compare_emojis_subtitle">"確認顯示在其他工作階段上的表情符號是否和下方的相同。"</string>
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<string name="state_event_room_join_by_you">"Vous avez rejoint le salon"</string>
|
||||
<string name="state_event_room_knock">"%1$s a demandé à rejoindre"</string>
|
||||
<string name="state_event_room_knock_accepted">"%1$s a autorisé %2$s à rejoindre"</string>
|
||||
<string name="state_event_room_knock_accepted_by_you">"Vous avez autoriser %1$s à joindre le salon"</string>
|
||||
<string name="state_event_room_knock_accepted_by_you">"Vous avez autorisé %1$s à joindre le salon"</string>
|
||||
<string name="state_event_room_knock_by_you">"Vous avez demandé à rejoindre"</string>
|
||||
<string name="state_event_room_knock_denied">"%1$s a rejeté la demande de %2$s pour rejoindre"</string>
|
||||
<string name="state_event_room_knock_denied_by_you">"Vous avez rejeté la demande de %1$s pour rejoindre"</string>
|
||||
|
||||
@@ -244,6 +244,7 @@
|
||||
<string name="error_failed_loading_messages">"Načítání zpráv se nezdařilo"</string>
|
||||
<string name="error_failed_locating_user">"%1$s nemá přístup k vaší poloze. Zkuste to prosím později."</string>
|
||||
<string name="error_failed_uploading_voice_message">"Nepodařilo se nahrát hlasovou zprávu."</string>
|
||||
<string name="error_message_not_found">"Zpráva nebyla nalezena"</string>
|
||||
<string name="error_missing_location_auth_android">"%1$s nemá oprávnění k přístupu k vaší poloze. Přístup můžete povolit v Nastavení."</string>
|
||||
<string name="error_missing_location_rationale_android">"%1$s nemá oprávnění k přístupu k vaší poloze. Povolit přístup níže."</string>
|
||||
<string name="error_missing_microphone_voice_rationale_android">"%1$s nemá oprávnění k přístupu k mikrofonu. Povolte přístup k nahrávání hlasové zprávy."</string>
|
||||
|
||||
@@ -158,7 +158,7 @@
|
||||
<string name="common_modern">"Modern"</string>
|
||||
<string name="common_mute">"Stummschalten"</string>
|
||||
<string name="common_no_results">"Keine Ergebnisse"</string>
|
||||
<string name="common_no_room_name">"Kein Raum-Name"</string>
|
||||
<string name="common_no_room_name">"Kein Raumname"</string>
|
||||
<string name="common_offline">"Offline"</string>
|
||||
<string name="common_or">"oder"</string>
|
||||
<string name="common_password">"Passwort"</string>
|
||||
@@ -177,9 +177,7 @@
|
||||
<string name="common_privacy_policy">"Datenschutzerklärung"</string>
|
||||
<string name="common_reaction">"Reaktion"</string>
|
||||
<string name="common_reactions">"Reaktionen"</string>
|
||||
<string name="common_recovery_key">
|
||||
<b>"Wiederherstellungsschlüssel"</b>
|
||||
</string>
|
||||
<string name="common_recovery_key">"Wiederherstellungsschlüssel"</string>
|
||||
<string name="common_refreshing">"Wird erneuert…"</string>
|
||||
<string name="common_replying_to">"%1$s antworten"</string>
|
||||
<string name="common_report_a_bug">"Einen Fehler melden"</string>
|
||||
@@ -187,7 +185,7 @@
|
||||
<string name="common_report_submitted">"Bericht eingereicht"</string>
|
||||
<string name="common_rich_text_editor">"Rich-Text-Editor"</string>
|
||||
<string name="common_room">"Raum"</string>
|
||||
<string name="common_room_name">"Raum-Name"</string>
|
||||
<string name="common_room_name">"Raumname"</string>
|
||||
<string name="common_room_name_placeholder">"z.B. dein Projektname"</string>
|
||||
<string name="common_saved_changes">"Gespeicherte Änderungen"</string>
|
||||
<string name="common_saving">"Speichern"</string>
|
||||
@@ -242,6 +240,7 @@
|
||||
<string name="error_failed_loading_messages">"Fehler beim Laden der Nachrichten"</string>
|
||||
<string name="error_failed_locating_user">"%1$s konnte nicht auf deinen Standort zugreifen. Bitte versuche es später erneut."</string>
|
||||
<string name="error_failed_uploading_voice_message">"Fehler beim Hochladen der Sprachnachricht."</string>
|
||||
<string name="error_message_not_found">"Nachricht nicht gefunden"</string>
|
||||
<string name="error_missing_location_auth_android">"%1$s hat keine Erlaubnis, auf deinen Standort zuzugreifen. Du kannst den Zugriff in den Einstellungen aktivieren."</string>
|
||||
<string name="error_missing_location_rationale_android">"%1$s hat keine Erlaubnis, auf deinen Standort zuzugreifen. Aktiviere unten den Zugriff."</string>
|
||||
<string name="error_missing_microphone_voice_rationale_android">"%1$s hat nicht die Erlaubnis auf dein Mikrofon zuzugreifen. Aktiviere den Zugriff, um eine Sprachnachricht aufzunehmen."</string>
|
||||
|
||||
@@ -240,6 +240,7 @@
|
||||
<string name="error_failed_loading_messages">"Nem sikerült betölteni az üzeneteket"</string>
|
||||
<string name="error_failed_locating_user">"Az %1$s nem tudta elérni a tartózkodási helyét. Próbálja újra később."</string>
|
||||
<string name="error_failed_uploading_voice_message">"Nem sikerült feltölteni a hangüzenetét."</string>
|
||||
<string name="error_message_not_found">"Az üzenet nem található"</string>
|
||||
<string name="error_missing_location_auth_android">"Az %1$snek nincs engedélye, hogy hozzáférjen a tartózkodási helyéhez. Ezt a beállításokban engedélyezheti."</string>
|
||||
<string name="error_missing_location_rationale_android">"Az %1$snek nincs engedélye, hogy hozzáférjen a tartózkodási helyéhez. Engedélyezze alább az elérését."</string>
|
||||
<string name="error_missing_microphone_voice_rationale_android">"Az %1$snek nincs engedélye, hogy hozzáférjen a mikrofonhoz. Engedélyezze, hogy tudjon hangüzenetet felvenni."</string>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
<string name="a11y_poll_end">"Avslutade omröstning"</string>
|
||||
<string name="a11y_send_files">"Skicka filer"</string>
|
||||
<string name="a11y_show_password">"Visa lösenord"</string>
|
||||
<string name="a11y_start_call">"Starta ett samtal"</string>
|
||||
<string name="a11y_user_menu">"Användarmeny"</string>
|
||||
<string name="a11y_voice_message_record">"Spela in röstmeddelande."</string>
|
||||
<string name="action_accept">"Godkänn"</string>
|
||||
@@ -39,6 +40,7 @@
|
||||
<string name="action_edit_poll">"Redigera omröstning"</string>
|
||||
<string name="action_enable">"Aktivera"</string>
|
||||
<string name="action_end_poll">"Avsluta omröstning"</string>
|
||||
<string name="action_enter_pin">"Ange PIN-kod"</string>
|
||||
<string name="action_forgot_password">"Glömt lösenordet?"</string>
|
||||
<string name="action_forward">"Vidarebefordra"</string>
|
||||
<string name="action_invite">"Bjud in"</string>
|
||||
@@ -146,8 +148,10 @@
|
||||
<string name="common_refreshing">"Uppdaterar …"</string>
|
||||
<string name="common_replying_to">"Svarar till %1$s"</string>
|
||||
<string name="common_report_a_bug">"Rapportera en bugg"</string>
|
||||
<string name="common_report_a_problem">"Rapportera ett problem"</string>
|
||||
<string name="common_report_submitted">"Rapport inskickad"</string>
|
||||
<string name="common_rich_text_editor">"Riktextredigerare"</string>
|
||||
<string name="common_room">"Rum"</string>
|
||||
<string name="common_room_name">"Rumsnamn"</string>
|
||||
<string name="common_room_name_placeholder">"t.ex. ditt projektnamn"</string>
|
||||
<string name="common_screen_lock">"Skärmlås"</string>
|
||||
@@ -159,6 +163,7 @@
|
||||
<string name="common_server_url">"Server-URL"</string>
|
||||
<string name="common_settings">"Inställningar"</string>
|
||||
<string name="common_shared_location">"Delade plats"</string>
|
||||
<string name="common_signing_out">"Loggar ut"</string>
|
||||
<string name="common_starting_chat">"Startar chatt …"</string>
|
||||
<string name="common_sticker">"Dekal"</string>
|
||||
<string name="common_success">"Lyckades"</string>
|
||||
@@ -178,9 +183,11 @@
|
||||
<string name="common_username">"Användarnamn"</string>
|
||||
<string name="common_verification_cancelled">"Verifiering avbruten"</string>
|
||||
<string name="common_verification_complete">"Verifieringen slutförd"</string>
|
||||
<string name="common_verify_device">"Verifiera enheten"</string>
|
||||
<string name="common_video">"Video"</string>
|
||||
<string name="common_voice_message">"Röstmeddelande"</string>
|
||||
<string name="common_waiting">"Väntar …"</string>
|
||||
<string name="common_waiting_for_decryption_key">"Väntar på detta meddelande"</string>
|
||||
<string name="dialog_title_confirmation">"Bekräftelse"</string>
|
||||
<string name="dialog_title_error">"Fel"</string>
|
||||
<string name="dialog_title_success">"Lyckades"</string>
|
||||
|
||||
@@ -145,16 +145,21 @@
|
||||
"includeRegex" : [
|
||||
"screen_room_details_.*",
|
||||
"screen_room_member_list_.*",
|
||||
"screen_dm_details_.*",
|
||||
"screen_room_notification_settings_.*",
|
||||
"screen_notification_settings_edit_failed_updating_default_mode",
|
||||
"screen_polls_history_title",
|
||||
"screen_notification_settings_mentions_only_disclaimer",
|
||||
"screen_start_chat_error_starting_chat",
|
||||
"screen_room_change_.*",
|
||||
"screen_room_roles_.*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name" : ":features:userprofile:shared",
|
||||
"includeRegex" : [
|
||||
"screen_start_chat_error_starting_chat",
|
||||
"screen_dm_details_.*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name" : ":features:messages:impl",
|
||||
"includeRegex" : [
|
||||
|
||||
Reference in New Issue
Block a user