forked from dsutanto/bChot-android
First Commit
This commit is contained in:
20
features/knockrequests/api/build.gradle.kts
Normal file
20
features/knockrequests/api/build.gradle.kts
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.knockrequests.api"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.api.banner
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
interface KnockRequestsBannerRenderer {
|
||||
@Composable
|
||||
fun View(modifier: Modifier, onViewRequestsClick: () -> Unit)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.api.list
|
||||
|
||||
import io.element.android.libraries.architecture.SimpleFeatureEntryPoint
|
||||
|
||||
interface KnockRequestsListEntryPoint : SimpleFeatureEntryPoint
|
||||
41
features/knockrequests/impl/build.gradle.kts
Normal file
41
features/knockrequests/impl/build.gradle.kts
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import extension.setupDependencyInjection
|
||||
import extension.testCommonDependencies
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.knockrequests.impl"
|
||||
testOptions {
|
||||
unitTests {
|
||||
isIncludeAndroidResources = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupDependencyInjection()
|
||||
|
||||
dependencies {
|
||||
api(projects.features.knockrequests.api)
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.matrixui)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
implementation(projects.libraries.designsystem)
|
||||
implementation(projects.libraries.featureflag.api)
|
||||
|
||||
testCommonDependencies(libs, true)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.featureflag.test)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.impl.banner
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.features.knockrequests.api.banner.KnockRequestsBannerRenderer
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
|
||||
@ContributesBinding(RoomScope::class)
|
||||
class DefaultKnockRequestsBannerRenderer(
|
||||
private val presenter: KnockRequestsBannerPresenter,
|
||||
) : KnockRequestsBannerRenderer {
|
||||
@Composable
|
||||
override fun View(modifier: Modifier, onViewRequestsClick: () -> Unit) {
|
||||
val state = presenter.present()
|
||||
KnockRequestsBannerView(
|
||||
state = state,
|
||||
onViewRequestsClick = onViewRequestsClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.impl.banner
|
||||
|
||||
sealed interface KnockRequestsBannerEvents {
|
||||
data object AcceptSingleRequest : KnockRequestsBannerEvents
|
||||
data object Dismiss : KnockRequestsBannerEvents
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.impl.banner
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable
|
||||
import io.element.android.features.knockrequests.impl.data.KnockRequestsService
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.coroutine.mapState
|
||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private const val ACCEPT_ERROR_DISPLAY_DURATION = 1500L
|
||||
|
||||
@Inject
|
||||
class KnockRequestsBannerPresenter(
|
||||
private val knockRequestsService: KnockRequestsService,
|
||||
@SessionCoroutineScope
|
||||
private val sessionCoroutineScope: CoroutineScope,
|
||||
) : Presenter<KnockRequestsBannerState> {
|
||||
@Composable
|
||||
override fun present(): KnockRequestsBannerState {
|
||||
val knockRequests by remember {
|
||||
knockRequestsService.knockRequestsFlow.mapState { knockRequests ->
|
||||
knockRequests.dataOrNull().orEmpty()
|
||||
.filter { !it.isSeen }
|
||||
.toImmutableList()
|
||||
}
|
||||
}.collectAsState()
|
||||
|
||||
val permissions by knockRequestsService.permissionsFlow.collectAsState()
|
||||
val showAcceptError = remember { mutableStateOf(false) }
|
||||
|
||||
val shouldShowBanner by remember {
|
||||
derivedStateOf {
|
||||
permissions.canHandle && knockRequests.isNotEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
fun handleEvent(event: KnockRequestsBannerEvents) {
|
||||
when (event) {
|
||||
is KnockRequestsBannerEvents.AcceptSingleRequest -> {
|
||||
sessionCoroutineScope.acceptSingleKnockRequest(
|
||||
knockRequests = knockRequests,
|
||||
displayAcceptError = showAcceptError,
|
||||
)
|
||||
}
|
||||
is KnockRequestsBannerEvents.Dismiss -> {
|
||||
sessionCoroutineScope.launch {
|
||||
knockRequestsService.markAllKnockRequestsAsSeen()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return KnockRequestsBannerState(
|
||||
knockRequests = knockRequests,
|
||||
displayAcceptError = showAcceptError.value,
|
||||
canAccept = permissions.canAccept,
|
||||
isVisible = shouldShowBanner,
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.acceptSingleKnockRequest(
|
||||
knockRequests: List<KnockRequestPresentable>,
|
||||
displayAcceptError: MutableState<Boolean>,
|
||||
) = launch {
|
||||
val knockRequest = knockRequests.singleOrNull()
|
||||
if (knockRequest != null) {
|
||||
knockRequestsService.acceptKnockRequest(knockRequest, optimistic = true)
|
||||
.onFailure {
|
||||
displayAcceptError.value = true
|
||||
delay(ACCEPT_ERROR_DISPLAY_DURATION)
|
||||
displayAcceptError.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.impl.banner
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import io.element.android.features.knockrequests.impl.R
|
||||
import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
data class KnockRequestsBannerState(
|
||||
val isVisible: Boolean,
|
||||
val knockRequests: ImmutableList<KnockRequestPresentable>,
|
||||
val displayAcceptError: Boolean,
|
||||
val canAccept: Boolean,
|
||||
val eventSink: (KnockRequestsBannerEvents) -> Unit,
|
||||
) {
|
||||
val subtitle = knockRequests.singleOrNull()?.userId?.value
|
||||
val reason = knockRequests.singleOrNull()?.reason
|
||||
|
||||
@Composable
|
||||
fun formattedTitle(): String {
|
||||
return when (knockRequests.size) {
|
||||
0 -> ""
|
||||
1 -> stringResource(R.string.screen_room_single_knock_request_title, knockRequests.first().getBestName())
|
||||
else -> {
|
||||
val firstRequest = knockRequests.first()
|
||||
val otherRequestsCount = knockRequests.size - 1
|
||||
pluralStringResource(
|
||||
id = R.plurals.screen_room_multiple_knock_requests_title,
|
||||
count = otherRequestsCount,
|
||||
firstRequest.getBestName(),
|
||||
otherRequestsCount
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.impl.banner
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable
|
||||
import io.element.android.features.knockrequests.impl.data.aKnockRequestPresentable
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
class KnockRequestsBannerStateProvider : PreviewParameterProvider<KnockRequestsBannerState> {
|
||||
override val values: Sequence<KnockRequestsBannerState>
|
||||
get() = sequenceOf(
|
||||
aKnockRequestsBannerState(),
|
||||
aKnockRequestsBannerState(
|
||||
knockRequests = listOf(
|
||||
aKnockRequestPresentable(
|
||||
reason = "A very long reason that should probably be truncated, " +
|
||||
"but could be also expanded so you can see it over the lines, wow," +
|
||||
"very amazing reason, I know, right, I'm so good at writing reasons."
|
||||
)
|
||||
)
|
||||
),
|
||||
aKnockRequestsBannerState(
|
||||
knockRequests = listOf(
|
||||
aKnockRequestPresentable(),
|
||||
aKnockRequestPresentable(displayName = "Alice")
|
||||
)
|
||||
),
|
||||
aKnockRequestsBannerState(
|
||||
knockRequests = listOf(
|
||||
aKnockRequestPresentable(),
|
||||
aKnockRequestPresentable(displayName = "Alice"),
|
||||
aKnockRequestPresentable(displayName = "Bob"),
|
||||
aKnockRequestPresentable(displayName = "Charlie")
|
||||
)
|
||||
),
|
||||
aKnockRequestsBannerState(
|
||||
canAccept = false
|
||||
),
|
||||
aKnockRequestsBannerState(
|
||||
displayAcceptError = true
|
||||
),
|
||||
aKnockRequestsBannerState(
|
||||
knockRequests = listOf(
|
||||
aKnockRequestPresentable(
|
||||
displayName = "A_very_long_display_name_so_that_the_text_can_be_displayed_on_multiple_lines"
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun aKnockRequestsBannerState(
|
||||
knockRequests: List<KnockRequestPresentable> = listOf(aKnockRequestPresentable()),
|
||||
displayAcceptError: Boolean = false,
|
||||
canAccept: Boolean = true,
|
||||
isVisible: Boolean = true,
|
||||
eventSink: (KnockRequestsBannerEvents) -> Unit = {}
|
||||
) = KnockRequestsBannerState(
|
||||
knockRequests = knockRequests.toImmutableList(),
|
||||
displayAcceptError = displayAcceptError,
|
||||
canAccept = canAccept,
|
||||
isVisible = isVisible,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.impl.banner
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.knockrequests.impl.R
|
||||
import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncIndicator
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncIndicatorHost
|
||||
import io.element.android.libraries.designsystem.components.async.rememberAsyncIndicatorState
|
||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarRow
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.ButtonSize
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Surface
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
private const val MAX_AVATAR_COUNT = 3
|
||||
|
||||
@Composable
|
||||
fun KnockRequestsBannerView(
|
||||
state: KnockRequestsBannerState,
|
||||
onViewRequestsClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(modifier = modifier) {
|
||||
AnimatedVisibility(
|
||||
visible = state.isVisible,
|
||||
enter = expandVertically(),
|
||||
exit = shrinkVertically(),
|
||||
) {
|
||||
Surface(
|
||||
shape = MaterialTheme.shapes.small,
|
||||
color = ElementTheme.colors.bgCanvasDefaultLevel1,
|
||||
shadowElevation = 24.dp,
|
||||
modifier = Modifier.padding(16.dp),
|
||||
) {
|
||||
KnockRequestsBannerContent(
|
||||
state = state,
|
||||
onViewRequestsClick = onViewRequestsClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
KnockRequestsAcceptErrorView(displayError = state.displayAcceptError)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun KnockRequestsAcceptErrorView(
|
||||
displayError: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val asyncIndicatorState = rememberAsyncIndicatorState()
|
||||
AsyncIndicatorHost(modifier = modifier.statusBarsPadding(), state = asyncIndicatorState)
|
||||
LaunchedEffect(displayError) {
|
||||
if (displayError) {
|
||||
asyncIndicatorState.enqueue {
|
||||
AsyncIndicator.Custom(text = stringResource(CommonStrings.error_unknown))
|
||||
}
|
||||
} else {
|
||||
asyncIndicatorState.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun KnockRequestsBannerContent(
|
||||
state: KnockRequestsBannerState,
|
||||
onViewRequestsClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
fun onDismissClick() {
|
||||
state.eventSink(KnockRequestsBannerEvents.Dismiss)
|
||||
}
|
||||
|
||||
fun onAcceptClick() {
|
||||
state.eventSink(KnockRequestsBannerEvents.AcceptSingleRequest)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.padding(all = 16.dp)
|
||||
) {
|
||||
Row {
|
||||
KnockRequestAvatarView(
|
||||
state.knockRequests,
|
||||
modifier = Modifier.padding(top = 2.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = state.formattedTitle(),
|
||||
style = ElementTheme.typography.fontBodyMdMedium,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
textAlign = TextAlign.Start,
|
||||
)
|
||||
if (state.subtitle != null) {
|
||||
Text(
|
||||
text = state.subtitle,
|
||||
style = ElementTheme.typography.fontBodySmRegular,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
textAlign = TextAlign.Start,
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
Icon(
|
||||
modifier = Modifier.clickable(onClick = ::onDismissClick),
|
||||
imageVector = CompoundIcons.Close(),
|
||||
contentDescription = stringResource(CommonStrings.action_close)
|
||||
)
|
||||
}
|
||||
val reason = state.reason
|
||||
if (!reason.isNullOrEmpty()) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
text = state.reason,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
if (state.knockRequests.size > 1) {
|
||||
Button(
|
||||
text = stringResource(R.string.screen_room_multiple_knock_requests_view_all_button_title),
|
||||
onClick = onViewRequestsClick,
|
||||
size = ButtonSize.MediumLowPadding,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
} else {
|
||||
OutlinedButton(
|
||||
text = stringResource(R.string.screen_room_single_knock_request_view_button_title),
|
||||
onClick = onViewRequestsClick,
|
||||
size = ButtonSize.MediumLowPadding,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
if (state.canAccept) {
|
||||
Button(
|
||||
text = stringResource(R.string.screen_room_single_knock_request_accept_button_title),
|
||||
onClick = ::onAcceptClick,
|
||||
size = ButtonSize.MediumLowPadding,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun KnockRequestAvatarView(
|
||||
knockRequests: ImmutableList<KnockRequestPresentable>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(modifier) {
|
||||
when (knockRequests.size) {
|
||||
0 -> Unit
|
||||
1 -> Avatar(
|
||||
avatarData = knockRequests.first().getAvatarData(AvatarSize.KnockRequestBanner),
|
||||
avatarType = AvatarType.User,
|
||||
)
|
||||
else -> KnockRequestAvatarListView(knockRequests)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun KnockRequestAvatarListView(
|
||||
knockRequests: ImmutableList<KnockRequestPresentable>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val avatars = knockRequests
|
||||
.take(MAX_AVATAR_COUNT)
|
||||
.map { knockRequest ->
|
||||
knockRequest.getAvatarData(AvatarSize.KnockRequestBanner)
|
||||
}
|
||||
.toImmutableList()
|
||||
AvatarRow(
|
||||
avatarDataList = avatars,
|
||||
avatarType = AvatarType.User,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@PreviewsDayNight
|
||||
internal fun KnockRequestsBannerViewPreview(@PreviewParameter(KnockRequestsBannerStateProvider::class) state: KnockRequestsBannerState) = ElementPreview {
|
||||
KnockRequestsBannerView(
|
||||
state = state,
|
||||
onViewRequestsClick = {},
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.impl.data
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
|
||||
fun aKnockRequestPresentable(
|
||||
eventId: EventId = EventId("\$eventId"),
|
||||
userId: UserId = UserId("@jacob_ross:example.com"),
|
||||
displayName: String? = "Jacob Ross",
|
||||
avatarUrl: String? = null,
|
||||
reason: String? = "Hi, I would like to get access to this room please.",
|
||||
formattedDate: String? = "20 Nov 2024",
|
||||
) = object : KnockRequestPresentable {
|
||||
override val eventId: EventId = eventId
|
||||
override val userId: UserId = userId
|
||||
override val displayName: String? = displayName
|
||||
override val avatarUrl: String? = avatarUrl
|
||||
override val reason: String? = reason
|
||||
override val formattedDate: String? = formattedDate
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.impl.data
|
||||
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.canBan
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.canInvite
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.canKick
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
data class KnockRequestPermissions(
|
||||
val canAccept: Boolean,
|
||||
val canDecline: Boolean,
|
||||
val canBan: Boolean,
|
||||
) {
|
||||
val canHandle = canAccept || canDecline || canBan
|
||||
}
|
||||
|
||||
fun JoinedRoom.knockRequestPermissionsFlow(): Flow<KnockRequestPermissions> {
|
||||
return syncUpdateFlow.map {
|
||||
val canAccept = canInvite().getOrDefault(false)
|
||||
val canDecline = canKick().getOrDefault(false)
|
||||
val canBan = canBan().getOrDefault(false)
|
||||
KnockRequestPermissions(canAccept, canDecline, canBan)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.impl.data
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
|
||||
@Immutable
|
||||
interface KnockRequestPresentable {
|
||||
val eventId: EventId
|
||||
val userId: UserId
|
||||
val displayName: String?
|
||||
val avatarUrl: String?
|
||||
val reason: String?
|
||||
val formattedDate: String?
|
||||
|
||||
fun getAvatarData(size: AvatarSize) = AvatarData(
|
||||
id = userId.value,
|
||||
name = displayName,
|
||||
url = avatarUrl,
|
||||
size = size,
|
||||
)
|
||||
|
||||
fun getBestName(): String {
|
||||
return displayName?.takeIf { it.isNotEmpty() } ?: userId.value
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.impl.data
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.knock.KnockRequest
|
||||
|
||||
class KnockRequestWrapper(
|
||||
private val inner: KnockRequest,
|
||||
dateFormatter: (Long?) -> String? = { null }
|
||||
) : KnockRequestPresentable {
|
||||
override val eventId: EventId = inner.eventId
|
||||
override val userId: UserId = inner.userId
|
||||
override val displayName: String? = inner.displayName
|
||||
override val avatarUrl: String? = inner.avatarUrl
|
||||
override val reason: String? = inner.reason?.trim()
|
||||
override val formattedDate: String? = dateFormatter(inner.timestamp)
|
||||
|
||||
val isSeen: Boolean = inner.isSeen
|
||||
|
||||
suspend fun accept(): Result<Unit> = inner.accept()
|
||||
|
||||
suspend fun decline(reason: String?): Result<Unit> = inner.decline(reason)
|
||||
|
||||
suspend fun declineAndBan(reason: String?): Result<Unit> = inner.declineAndBan(reason)
|
||||
|
||||
suspend fun markAsSeen(): Result<Unit> = inner.markAsSeen()
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.impl.data
|
||||
|
||||
sealed class KnockRequestsException : Exception() {
|
||||
data object AcceptAllPartiallyFailed : KnockRequestsException()
|
||||
data object KnockRequestNotFound : KnockRequestsException()
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.impl.data
|
||||
|
||||
import dev.zacsweers.metro.BindingContainer
|
||||
import dev.zacsweers.metro.ContributesTo
|
||||
import dev.zacsweers.metro.Provides
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
|
||||
@BindingContainer
|
||||
@ContributesTo(RoomScope::class)
|
||||
object KnockRequestsModule {
|
||||
@Provides
|
||||
@SingleIn(RoomScope::class)
|
||||
fun knockRequestsService(room: JoinedRoom, featureFlagService: FeatureFlagService): KnockRequestsService {
|
||||
return KnockRequestsService(
|
||||
knockRequestsFlow = room.knockRequestsFlow,
|
||||
permissionsFlow = room.knockRequestPermissionsFlow(),
|
||||
isKnockFeatureEnabledFlow = featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock),
|
||||
coroutineScope = room.roomCoroutineScope
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.impl.data
|
||||
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.room.knock.KnockRequest
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.getAndUpdate
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.supervisorScope
|
||||
|
||||
class KnockRequestsService(
|
||||
knockRequestsFlow: Flow<List<KnockRequest>>,
|
||||
permissionsFlow: Flow<KnockRequestPermissions>,
|
||||
isKnockFeatureEnabledFlow: Flow<Boolean>,
|
||||
coroutineScope: CoroutineScope,
|
||||
) {
|
||||
// Keep track of the knock requests that have been handled, so we don't have to wait for sync to remove them.
|
||||
private val handledKnockRequestIds = MutableStateFlow<Set<EventId>>(emptySet())
|
||||
|
||||
val knockRequestsFlow = combine(
|
||||
isKnockFeatureEnabledFlow,
|
||||
knockRequestsFlow,
|
||||
handledKnockRequestIds,
|
||||
) { isKnockEnabled, knockRequests, handledKnockIds ->
|
||||
if (!isKnockEnabled) {
|
||||
AsyncData.Success(persistentListOf())
|
||||
} else {
|
||||
val presentableKnockRequests = knockRequests
|
||||
.filter { it.eventId !in handledKnockIds }
|
||||
.map { inner -> KnockRequestWrapper(inner) }
|
||||
.toImmutableList()
|
||||
AsyncData.Success(presentableKnockRequests)
|
||||
}
|
||||
}.stateIn(coroutineScope, SharingStarted.Lazily, AsyncData.Loading())
|
||||
|
||||
val permissionsFlow = permissionsFlow.stateIn(
|
||||
scope = coroutineScope,
|
||||
started = SharingStarted.Lazily,
|
||||
initialValue = KnockRequestPermissions(canAccept = false, canDecline = false, canBan = false)
|
||||
)
|
||||
|
||||
private fun knockRequestsList() = knockRequestsFlow.value.dataOrNull().orEmpty()
|
||||
|
||||
private fun getKnockRequestById(eventId: EventId): KnockRequestWrapper? {
|
||||
return knockRequestsList().find { it.eventId == eventId }
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept a knock request.
|
||||
* @param knockRequest The knock request to accept.
|
||||
* @param optimistic If true, the request will be marked as handled before the server responds.
|
||||
*/
|
||||
suspend fun acceptKnockRequest(knockRequest: KnockRequestPresentable, optimistic: Boolean = false): Result<Unit> {
|
||||
val wrapped = getKnockRequestById(knockRequest.eventId) ?: return knockRequestNotFoundResult()
|
||||
return handleKnockRequest(wrapped, optimistic) { accept() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Decline a knock request.
|
||||
* @param knockRequest The knock request to decline.
|
||||
* @param optimistic If true, the request will be marked as handled before the server responds.
|
||||
*/
|
||||
suspend fun declineKnockRequest(knockRequest: KnockRequestPresentable, optimistic: Boolean = false): Result<Unit> {
|
||||
val wrapped = getKnockRequestById(knockRequest.eventId) ?: return knockRequestNotFoundResult()
|
||||
return handleKnockRequest(wrapped, optimistic) { decline(null) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Decline a knock request by banning the user.
|
||||
* @param knockRequest The knock request to decline.
|
||||
* @param optimistic If true, the request will be marked as handled before the server responds.
|
||||
*/
|
||||
suspend fun declineAndBanKnockRequest(knockRequest: KnockRequestPresentable, optimistic: Boolean = false): Result<Unit> {
|
||||
val wrapped = getKnockRequestById(knockRequest.eventId) ?: return knockRequestNotFoundResult()
|
||||
return handleKnockRequest(wrapped, optimistic) { declineAndBan(null) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept all currently known knock requests.
|
||||
* @param optimistic If true, the requests will be marked as handled before the server responds.
|
||||
*/
|
||||
suspend fun acceptAllKnockRequests(optimistic: Boolean = false): Result<Unit> = supervisorScope {
|
||||
val results = knockRequestsList()
|
||||
.map { knockRequest ->
|
||||
async {
|
||||
acceptKnockRequest(knockRequest, optimistic = optimistic)
|
||||
}
|
||||
}
|
||||
.awaitAll()
|
||||
if (results.all { it.isSuccess }) {
|
||||
Result.success(Unit)
|
||||
} else {
|
||||
Result.failure(KnockRequestsException.AcceptAllPartiallyFailed)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark all currently known knock requests as seen.
|
||||
*/
|
||||
suspend fun markAllKnockRequestsAsSeen() = supervisorScope {
|
||||
knockRequestsList()
|
||||
.map { knockRequest ->
|
||||
async { knockRequest.markAsSeen() }
|
||||
}
|
||||
.awaitAll()
|
||||
}
|
||||
|
||||
private suspend fun handleKnockRequest(
|
||||
knockRequest: KnockRequestWrapper,
|
||||
optimistic: Boolean,
|
||||
action: suspend (KnockRequestWrapper.() -> Result<Unit>)
|
||||
): Result<Unit> {
|
||||
if (optimistic) {
|
||||
handledKnockRequestIds.getAndUpdate { it + knockRequest.eventId }
|
||||
}
|
||||
return action(knockRequest)
|
||||
.onFailure {
|
||||
if (optimistic) {
|
||||
handledKnockRequestIds.getAndUpdate { it - knockRequest.eventId }
|
||||
}
|
||||
}
|
||||
.onSuccess {
|
||||
if (!optimistic) {
|
||||
handledKnockRequestIds.getAndUpdate { it + knockRequest.eventId }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun knockRequestNotFoundResult() = Result.failure<Unit>(KnockRequestsException.KnockRequestNotFound)
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.impl.list
|
||||
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultKnockRequestsListEntryPoint : KnockRequestsListEntryPoint {
|
||||
override fun createNode(parentNode: Node, buildContext: BuildContext): Node {
|
||||
return parentNode.createNode<KnockRequestsListNode>(buildContext)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.impl.list
|
||||
|
||||
import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable
|
||||
|
||||
sealed interface KnockRequestsListEvents {
|
||||
data class Accept(val knockRequest: KnockRequestPresentable) : KnockRequestsListEvents
|
||||
data class Decline(val knockRequest: KnockRequestPresentable) : KnockRequestsListEvents
|
||||
data class DeclineAndBan(val knockRequest: KnockRequestPresentable) : KnockRequestsListEvents
|
||||
data object AcceptAll : KnockRequestsListEvents
|
||||
data object ResetCurrentAction : KnockRequestsListEvents
|
||||
data object RetryCurrentAction : KnockRequestsListEvents
|
||||
data object ConfirmCurrentAction : KnockRequestsListEvents
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.impl.list
|
||||
|
||||
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 dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import io.element.android.annotations.ContributesNode
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
|
||||
@ContributesNode(RoomScope::class)
|
||||
@AssistedInject
|
||||
class KnockRequestsListNode(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val presenter: KnockRequestsListPresenter,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
KnockRequestsListView(
|
||||
state = state,
|
||||
onBackClick = ::navigateUp,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.impl.list
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
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 dev.zacsweers.metro.Inject
|
||||
import io.element.android.features.knockrequests.impl.data.KnockRequestsService
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runUpdatingState
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Inject
|
||||
class KnockRequestsListPresenter(
|
||||
private val knockRequestsService: KnockRequestsService,
|
||||
) : Presenter<KnockRequestsListState> {
|
||||
@Composable
|
||||
override fun present(): KnockRequestsListState {
|
||||
val asyncAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
|
||||
var currentAction by remember { mutableStateOf<KnockRequestsAction>(KnockRequestsAction.None) }
|
||||
|
||||
val permissions by knockRequestsService.permissionsFlow.collectAsState()
|
||||
val knockRequests by knockRequestsService.knockRequestsFlow.collectAsState()
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
fun handleEvent(event: KnockRequestsListEvents) {
|
||||
when (event) {
|
||||
KnockRequestsListEvents.AcceptAll -> {
|
||||
currentAction = KnockRequestsAction.AcceptAll
|
||||
}
|
||||
is KnockRequestsListEvents.Accept -> {
|
||||
currentAction = KnockRequestsAction.Accept(event.knockRequest)
|
||||
}
|
||||
is KnockRequestsListEvents.Decline -> {
|
||||
currentAction = KnockRequestsAction.Decline(event.knockRequest)
|
||||
}
|
||||
is KnockRequestsListEvents.DeclineAndBan -> {
|
||||
currentAction = KnockRequestsAction.DeclineAndBan(event.knockRequest)
|
||||
}
|
||||
KnockRequestsListEvents.ResetCurrentAction -> {
|
||||
asyncAction.value = AsyncAction.Uninitialized
|
||||
currentAction = KnockRequestsAction.None
|
||||
}
|
||||
KnockRequestsListEvents.RetryCurrentAction -> {
|
||||
coroutineScope.executeAction(currentAction, asyncAction, isActionConfirmed = true)
|
||||
}
|
||||
KnockRequestsListEvents.ConfirmCurrentAction -> {
|
||||
coroutineScope.executeAction(currentAction, asyncAction, isActionConfirmed = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
LaunchedEffect(currentAction) {
|
||||
executeAction(currentAction, asyncAction, isActionConfirmed = false)
|
||||
}
|
||||
|
||||
return KnockRequestsListState(
|
||||
knockRequests = knockRequests,
|
||||
currentAction = currentAction,
|
||||
permissions = permissions,
|
||||
asyncAction = asyncAction.value,
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.executeAction(
|
||||
currentAction: KnockRequestsAction,
|
||||
asyncAction: MutableState<AsyncAction<Unit>>,
|
||||
isActionConfirmed: Boolean,
|
||||
) = launch {
|
||||
when (currentAction) {
|
||||
is KnockRequestsAction.Accept -> {
|
||||
runUpdatingState(asyncAction) {
|
||||
knockRequestsService.acceptKnockRequest(currentAction.knockRequest)
|
||||
}
|
||||
}
|
||||
is KnockRequestsAction.Decline -> {
|
||||
if (isActionConfirmed) {
|
||||
runUpdatingState(asyncAction) {
|
||||
knockRequestsService.declineKnockRequest(currentAction.knockRequest)
|
||||
}
|
||||
} else {
|
||||
asyncAction.value = AsyncAction.ConfirmingNoParams
|
||||
}
|
||||
}
|
||||
is KnockRequestsAction.DeclineAndBan -> {
|
||||
if (isActionConfirmed) {
|
||||
runUpdatingState(asyncAction) {
|
||||
knockRequestsService.declineAndBanKnockRequest(currentAction.knockRequest)
|
||||
}
|
||||
} else {
|
||||
asyncAction.value = AsyncAction.ConfirmingNoParams
|
||||
}
|
||||
}
|
||||
is KnockRequestsAction.AcceptAll -> {
|
||||
if (isActionConfirmed) {
|
||||
runUpdatingState(asyncAction) {
|
||||
knockRequestsService.acceptAllKnockRequests()
|
||||
}
|
||||
} else {
|
||||
asyncAction.value = AsyncAction.ConfirmingNoParams
|
||||
}
|
||||
}
|
||||
KnockRequestsAction.None -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.impl.list
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.features.knockrequests.impl.data.KnockRequestPermissions
|
||||
import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
data class KnockRequestsListState(
|
||||
val knockRequests: AsyncData<ImmutableList<KnockRequestPresentable>>,
|
||||
val currentAction: KnockRequestsAction,
|
||||
val asyncAction: AsyncAction<Unit>,
|
||||
val permissions: KnockRequestPermissions,
|
||||
val eventSink: (KnockRequestsListEvents) -> Unit,
|
||||
) {
|
||||
val canAcceptAll = permissions.canAccept && knockRequests is AsyncData.Success && knockRequests.data.size > 1
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed interface KnockRequestsAction {
|
||||
data object None : KnockRequestsAction
|
||||
data class Accept(val knockRequest: KnockRequestPresentable) : KnockRequestsAction
|
||||
data class Decline(val knockRequest: KnockRequestPresentable) : KnockRequestsAction
|
||||
data class DeclineAndBan(val knockRequest: KnockRequestPresentable) : KnockRequestsAction
|
||||
data object AcceptAll : KnockRequestsAction
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.impl.list
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.knockrequests.impl.data.KnockRequestPermissions
|
||||
import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable
|
||||
import io.element.android.features.knockrequests.impl.data.aKnockRequestPresentable
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
open class KnockRequestsListStateProvider : PreviewParameterProvider<KnockRequestsListState> {
|
||||
override val values: Sequence<KnockRequestsListState>
|
||||
get() = sequenceOf(
|
||||
aKnockRequestsListState(
|
||||
knockRequests = AsyncData.Loading(),
|
||||
),
|
||||
aKnockRequestsListState(
|
||||
knockRequests = AsyncData.Success(
|
||||
persistentListOf()
|
||||
),
|
||||
),
|
||||
aKnockRequestsListState(
|
||||
knockRequests = AsyncData.Success(
|
||||
persistentListOf(
|
||||
aKnockRequestPresentable()
|
||||
)
|
||||
),
|
||||
),
|
||||
aKnockRequestsListState(
|
||||
knockRequests = AsyncData.Success(
|
||||
persistentListOf(
|
||||
aKnockRequestPresentable(
|
||||
reason = "A very long reason that should probably be truncated, " +
|
||||
"but could be also expanded so you can see it over the lines, wow," +
|
||||
"very amazing reason, I know, right, I'm so good at writing reasons."
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
aKnockRequestsListState(
|
||||
knockRequests = AsyncData.Success(
|
||||
persistentListOf(
|
||||
aKnockRequestPresentable(),
|
||||
aKnockRequestPresentable(
|
||||
userId = UserId("@user:example.com"),
|
||||
displayName = null,
|
||||
avatarUrl = null,
|
||||
reason = null,
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
aKnockRequestsListState(
|
||||
knockRequests = AsyncData.Success(
|
||||
persistentListOf(
|
||||
aKnockRequestPresentable()
|
||||
)
|
||||
),
|
||||
currentAction = KnockRequestsAction.AcceptAll,
|
||||
asyncAction = AsyncAction.ConfirmingNoParams,
|
||||
),
|
||||
aKnockRequestsListState(
|
||||
knockRequests = AsyncData.Success(
|
||||
persistentListOf(
|
||||
aKnockRequestPresentable()
|
||||
)
|
||||
),
|
||||
currentAction = KnockRequestsAction.AcceptAll,
|
||||
asyncAction = AsyncAction.Loading,
|
||||
),
|
||||
aKnockRequestsListState(
|
||||
knockRequests = AsyncData.Success(
|
||||
persistentListOf(
|
||||
aKnockRequestPresentable()
|
||||
)
|
||||
),
|
||||
permissions = KnockRequestPermissions(
|
||||
canAccept = false,
|
||||
canDecline = true,
|
||||
canBan = true,
|
||||
),
|
||||
currentAction = KnockRequestsAction.AcceptAll,
|
||||
asyncAction = AsyncAction.Failure(RuntimeException("Failed to accept all")),
|
||||
),
|
||||
aKnockRequestsListState(
|
||||
knockRequests = AsyncData.Success(
|
||||
persistentListOf(
|
||||
aKnockRequestPresentable()
|
||||
)
|
||||
),
|
||||
permissions = KnockRequestPermissions(
|
||||
canAccept = true,
|
||||
canDecline = false,
|
||||
canBan = true,
|
||||
),
|
||||
),
|
||||
aKnockRequestsListState(
|
||||
knockRequests = AsyncData.Success(
|
||||
persistentListOf(
|
||||
aKnockRequestPresentable()
|
||||
)
|
||||
),
|
||||
permissions = KnockRequestPermissions(
|
||||
canAccept = false,
|
||||
canDecline = false,
|
||||
canBan = true,
|
||||
),
|
||||
),
|
||||
aKnockRequestsListState(
|
||||
knockRequests = AsyncData.Success(
|
||||
persistentListOf(
|
||||
aKnockRequestPresentable()
|
||||
)
|
||||
),
|
||||
permissions = KnockRequestPermissions(
|
||||
canAccept = true,
|
||||
canDecline = true,
|
||||
canBan = false,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun aKnockRequestsListState(
|
||||
knockRequests: AsyncData<ImmutableList<KnockRequestPresentable>> = AsyncData.Success(persistentListOf()),
|
||||
currentAction: KnockRequestsAction = KnockRequestsAction.None,
|
||||
asyncAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
permissions: KnockRequestPermissions = KnockRequestPermissions(
|
||||
canAccept = true,
|
||||
canDecline = true,
|
||||
canBan = true,
|
||||
),
|
||||
eventSink: (KnockRequestsListEvents) -> Unit = {},
|
||||
) = KnockRequestsListState(
|
||||
knockRequests = knockRequests,
|
||||
currentAction = currentAction,
|
||||
asyncAction = asyncAction,
|
||||
permissions = permissions,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
@@ -0,0 +1,496 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.impl.list
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Arrangement.spacedBy
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.onSizeChanged
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.knockrequests.impl.R
|
||||
import io.element.android.features.knockrequests.impl.data.KnockRequestPresentable
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||
import io.element.android.libraries.designsystem.components.BigIcon
|
||||
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.text.toDp
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.ButtonSize
|
||||
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
|
||||
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Composable
|
||||
fun KnockRequestsListView(
|
||||
state: KnockRequestsListState,
|
||||
onBackClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
topBar = {
|
||||
KnockRequestsListTopBar(onBackClick = onBackClick)
|
||||
},
|
||||
content = { padding ->
|
||||
KnockRequestsListContent(
|
||||
state = state,
|
||||
modifier = Modifier
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding),
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun KnockRequestsListContent(
|
||||
state: KnockRequestsListState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
fun onAcceptClick(knockRequest: KnockRequestPresentable) {
|
||||
state.eventSink(KnockRequestsListEvents.Accept(knockRequest))
|
||||
}
|
||||
|
||||
fun onDeclineClick(knockRequest: KnockRequestPresentable) {
|
||||
state.eventSink(KnockRequestsListEvents.Decline(knockRequest))
|
||||
}
|
||||
|
||||
fun onBanClick(knockRequest: KnockRequestPresentable) {
|
||||
state.eventSink(KnockRequestsListEvents.DeclineAndBan(knockRequest))
|
||||
}
|
||||
|
||||
var bottomPaddingInPixels by remember { mutableIntStateOf(0) }
|
||||
|
||||
Box(modifier.fillMaxSize()) {
|
||||
when (state.knockRequests) {
|
||||
is AsyncData.Success -> {
|
||||
val knockRequests = state.knockRequests.data
|
||||
if (knockRequests.isEmpty()) {
|
||||
KnockRequestsEmptyList()
|
||||
} else {
|
||||
KnockRequestsList(
|
||||
knockRequests = knockRequests,
|
||||
canAccept = state.permissions.canAccept,
|
||||
canDecline = state.permissions.canDecline,
|
||||
canBan = state.permissions.canBan,
|
||||
onAcceptClick = ::onAcceptClick,
|
||||
onDeclineClick = ::onDeclineClick,
|
||||
onBanClick = ::onBanClick,
|
||||
contentPadding = PaddingValues(bottom = bottomPaddingInPixels.toDp()),
|
||||
)
|
||||
}
|
||||
}
|
||||
is AsyncData.Loading -> {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = spacedBy(16.dp),
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
) {
|
||||
CircularProgressIndicator(color = ElementTheme.colors.iconPrimary)
|
||||
Text(
|
||||
text = stringResource(R.string.screen_knock_requests_list_initial_loading_title),
|
||||
style = ElementTheme.typography.fontBodyLgRegular,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
KnockRequestsActionsView(
|
||||
currentAction = state.currentAction,
|
||||
asyncAction = state.asyncAction,
|
||||
onConfirm = {
|
||||
state.eventSink(KnockRequestsListEvents.ConfirmCurrentAction)
|
||||
},
|
||||
onRetry = {
|
||||
state.eventSink(KnockRequestsListEvents.RetryCurrentAction)
|
||||
},
|
||||
onDismiss = {
|
||||
state.eventSink(KnockRequestsListEvents.ResetCurrentAction)
|
||||
},
|
||||
)
|
||||
if (state.canAcceptAll) {
|
||||
KnockRequestsAcceptAll(
|
||||
onClick = {
|
||||
state.eventSink(KnockRequestsListEvents.AcceptAll)
|
||||
},
|
||||
onHeightChange = { height ->
|
||||
bottomPaddingInPixels = height
|
||||
},
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun KnockRequestsActionsView(
|
||||
currentAction: KnockRequestsAction,
|
||||
asyncAction: AsyncAction<Unit>,
|
||||
onConfirm: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
onRetry: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(modifier) {
|
||||
AsyncActionView(
|
||||
async = asyncAction,
|
||||
onSuccess = { onDismiss() },
|
||||
onErrorDismiss = onDismiss,
|
||||
confirmationDialog = {
|
||||
KnockRequestActionConfirmation(
|
||||
currentAction = currentAction,
|
||||
onSubmit = onConfirm,
|
||||
onDismiss = onDismiss,
|
||||
)
|
||||
},
|
||||
progressDialog = {
|
||||
KnockRequestActionProgress(target = currentAction)
|
||||
},
|
||||
errorMessage = {
|
||||
when (currentAction) {
|
||||
is KnockRequestsAction.Accept -> stringResource(R.string.screen_knock_requests_list_accept_failed_alert_description)
|
||||
is KnockRequestsAction.Decline -> stringResource(R.string.screen_knock_requests_list_decline_failed_alert_description)
|
||||
is KnockRequestsAction.DeclineAndBan -> stringResource(R.string.screen_knock_requests_list_decline_failed_alert_description)
|
||||
KnockRequestsAction.AcceptAll -> stringResource(R.string.screen_knock_requests_list_accept_all_failed_alert_description)
|
||||
else -> ""
|
||||
}
|
||||
},
|
||||
onRetry = onRetry,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun KnockRequestActionConfirmation(
|
||||
currentAction: KnockRequestsAction,
|
||||
onSubmit: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val (title, content, submitText) = when (currentAction) {
|
||||
KnockRequestsAction.AcceptAll -> Triple(
|
||||
stringResource(R.string.screen_knock_requests_list_accept_all_alert_title),
|
||||
stringResource(R.string.screen_knock_requests_list_accept_all_alert_description),
|
||||
stringResource(R.string.screen_knock_requests_list_accept_all_alert_confirm_button_title),
|
||||
)
|
||||
is KnockRequestsAction.Decline -> Triple(
|
||||
stringResource(R.string.screen_knock_requests_list_decline_alert_title),
|
||||
stringResource(R.string.screen_knock_requests_list_decline_alert_description, currentAction.knockRequest.getBestName()),
|
||||
stringResource(R.string.screen_knock_requests_list_decline_alert_confirm_button_title),
|
||||
)
|
||||
is KnockRequestsAction.DeclineAndBan -> Triple(
|
||||
stringResource(R.string.screen_knock_requests_list_ban_alert_title),
|
||||
stringResource(R.string.screen_knock_requests_list_ban_alert_description, currentAction.knockRequest.getBestName()),
|
||||
stringResource(R.string.screen_knock_requests_list_ban_alert_confirm_button_title),
|
||||
)
|
||||
else -> return
|
||||
}
|
||||
ConfirmationDialog(
|
||||
title = title,
|
||||
content = content,
|
||||
submitText = submitText,
|
||||
onSubmitClick = onSubmit,
|
||||
onDismiss = onDismiss,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun KnockRequestActionProgress(
|
||||
target: KnockRequestsAction,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val progressText = when (target) {
|
||||
is KnockRequestsAction.Accept -> stringResource(R.string.screen_knock_requests_list_accept_loading_title)
|
||||
is KnockRequestsAction.Decline -> stringResource(R.string.screen_knock_requests_list_decline_loading_title)
|
||||
is KnockRequestsAction.DeclineAndBan -> stringResource(R.string.screen_knock_requests_list_ban_loading_title)
|
||||
KnockRequestsAction.AcceptAll -> stringResource(R.string.screen_knock_requests_list_accept_all_loading_title)
|
||||
else -> return
|
||||
}
|
||||
ProgressDialog(
|
||||
text = progressText,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun KnockRequestsList(
|
||||
knockRequests: ImmutableList<KnockRequestPresentable>,
|
||||
canAccept: Boolean,
|
||||
canDecline: Boolean,
|
||||
canBan: Boolean,
|
||||
onAcceptClick: (KnockRequestPresentable) -> Unit,
|
||||
onDeclineClick: (KnockRequestPresentable) -> Unit,
|
||||
onBanClick: (KnockRequestPresentable) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
contentPadding: PaddingValues = PaddingValues(0.dp),
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
contentPadding = contentPadding,
|
||||
) {
|
||||
itemsIndexed(knockRequests) { index, knockRequest ->
|
||||
KnockRequestItem(
|
||||
knockRequest = knockRequest,
|
||||
onAcceptClick = onAcceptClick,
|
||||
canBan = canBan,
|
||||
canDecline = canDecline,
|
||||
canAccept = canAccept,
|
||||
onDeclineClick = onDeclineClick,
|
||||
onBanClick = onBanClick,
|
||||
)
|
||||
if (index != knockRequests.size - 1) {
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun KnockRequestItem(
|
||||
knockRequest: KnockRequestPresentable,
|
||||
canAccept: Boolean,
|
||||
canDecline: Boolean,
|
||||
canBan: Boolean,
|
||||
onAcceptClick: (KnockRequestPresentable) -> Unit,
|
||||
onDeclineClick: (KnockRequestPresentable) -> Unit,
|
||||
onBanClick: (KnockRequestPresentable) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||
) {
|
||||
Avatar(
|
||||
avatarData = knockRequest.getAvatarData(AvatarSize.KnockRequestItem),
|
||||
avatarType = AvatarType.User,
|
||||
)
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
Column {
|
||||
// Name and date
|
||||
Row {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.clipToBounds()
|
||||
.weight(1f),
|
||||
text = knockRequest.getBestName(),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
style = ElementTheme.typography.fontBodyLgMedium,
|
||||
)
|
||||
val formattedDate = knockRequest.formattedDate
|
||||
if (!formattedDate.isNullOrEmpty()) {
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Text(
|
||||
text = formattedDate,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
style = ElementTheme.typography.fontBodySmRegular,
|
||||
)
|
||||
}
|
||||
}
|
||||
// UserId
|
||||
if (!knockRequest.displayName.isNullOrEmpty()) {
|
||||
Text(
|
||||
text = knockRequest.userId.value,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
)
|
||||
}
|
||||
// Reason
|
||||
val reason = knockRequest.reason
|
||||
if (!reason.isNullOrBlank()) {
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
var isExpanded by rememberSaveable(knockRequest.userId) { mutableStateOf(false) }
|
||||
var isExpandable by rememberSaveable(knockRequest.userId) { mutableStateOf(false) }
|
||||
Row(
|
||||
verticalAlignment = Alignment.Top,
|
||||
modifier = Modifier
|
||||
.animateContentSize()
|
||||
.clickable(enabled = isExpandable) { isExpanded = !isExpanded }
|
||||
) {
|
||||
Text(
|
||||
text = reason,
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
maxLines = if (isExpanded) Int.MAX_VALUE else 3,
|
||||
onTextLayout = { result ->
|
||||
if (!isExpanded && result.hasVisualOverflow) {
|
||||
isExpandable = true
|
||||
}
|
||||
},
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
Box(modifier = Modifier.size(24.dp)) {
|
||||
if (isExpandable) {
|
||||
Icon(
|
||||
imageVector = if (isExpanded) CompoundIcons.ChevronUp() else CompoundIcons.ChevronDown(),
|
||||
contentDescription = null,
|
||||
tint = ElementTheme.colors.iconTertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Actions
|
||||
if (canDecline || canAccept) {
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
}
|
||||
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
if (canDecline) {
|
||||
OutlinedButton(
|
||||
text = stringResource(CommonStrings.action_decline),
|
||||
onClick = {
|
||||
onDeclineClick(knockRequest)
|
||||
},
|
||||
size = ButtonSize.MediumLowPadding,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
if (canAccept) {
|
||||
Button(
|
||||
text = stringResource(CommonStrings.action_accept),
|
||||
onClick = {
|
||||
onAcceptClick(knockRequest)
|
||||
},
|
||||
size = ButtonSize.MediumLowPadding,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
}
|
||||
}
|
||||
if (canBan) {
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
TextButton(
|
||||
text = stringResource(R.string.screen_knock_requests_list_decline_and_ban_action_title),
|
||||
onClick = {
|
||||
onBanClick(knockRequest)
|
||||
},
|
||||
destructive = true,
|
||||
size = ButtonSize.Small,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun KnockRequestsAcceptAll(
|
||||
onClick: () -> Unit,
|
||||
onHeightChange: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.shadow(elevation = 24.dp, spotColor = Color.Transparent)
|
||||
.background(color = ElementTheme.colors.bgCanvasDefault)
|
||||
.padding(vertical = 12.dp, horizontal = 16.dp)
|
||||
.onSizeChanged { onHeightChange(it.height) }
|
||||
) {
|
||||
OutlinedButton(
|
||||
text = stringResource(R.string.screen_knock_requests_list_accept_all_button_title),
|
||||
onClick = onClick,
|
||||
size = ButtonSize.Medium,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun KnockRequestsEmptyList(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier.padding(
|
||||
horizontal = 32.dp,
|
||||
vertical = 48.dp,
|
||||
),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
IconTitleSubtitleMolecule(
|
||||
title = stringResource(R.string.screen_knock_requests_list_empty_state_title),
|
||||
subTitle = stringResource(R.string.screen_knock_requests_list_empty_state_description),
|
||||
iconStyle = BigIcon.Style.Default(CompoundIcons.AskToJoin()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun KnockRequestsListTopBar(onBackClick: () -> Unit) {
|
||||
TopAppBar(
|
||||
titleStr = stringResource(R.string.screen_knock_requests_list_title),
|
||||
navigationIcon = { BackButton(onClick = onBackClick) },
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun KnockRequestsListViewPreview(
|
||||
@PreviewParameter(KnockRequestsListStateProvider::class) state: KnockRequestsListState
|
||||
) = ElementPreview {
|
||||
KnockRequestsListView(
|
||||
state = state,
|
||||
onBackClick = {},
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Прыняць"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Приемане"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Ano, přijmout všechny"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"Opravdu chcete přijmout všechny žádosti o vstup?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"Přijmout všechny požadavky"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"Přijmout vše"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Nemohli jsme přijmout všechny žádosti. Chcete to zkusit znovu?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Nepodařilo se přijmout všechny žádosti"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"Přijímání všech žádostí o vstup"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"Tuto žádost jsme nemohli přijmout. Chcete to zkusit znovu?"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"Žádost se nepodařilo přijmout"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"Přijímání žádosti o vstup"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Ano, odmítnout a vykázat"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"Opravdu chcete odmítnout a vykázat %1$s? Tento uživatel nebude moci znovu požádat o vstup do této místnosti."</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"Odmítnout a zakázat vstup"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"Odmítání vstupu a vykázání"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Ano, odmítnout"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"Opravdu chcete odmítnout %1$s žádost o vstup do této místnosti?"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"Odmítnout vstup"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Odmítnout a vykázat"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"Tuto žádost jsme nemohli odmítnout. Chcete to zkusit znovu?"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"Žádost se nepodařilo odmítnout"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"Odmítání žádosti o vstup"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"Když někdo požádá o vstup do místnosti, uvidíte jeho žádost zde."</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"Žádná čekající žádost o vstup"</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"Načítání žádostí o vstup…"</string>
|
||||
<string name="screen_knock_requests_list_title">"Žádosti o vstup"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="one">"%1$s +%2$d další chce vstoupit do této místnosti"</item>
|
||||
<item quantity="few">"%1$s +%2$d další chtějí vstoupit do této místnosti"</item>
|
||||
<item quantity="other">"%1$s +%2$d dalších chce vstoupit do této místnosti"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Zobrazit vše"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Přijmout"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s chce vstoupit do této místnosti"</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"Zobrazit"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Iawn, derbyn y cyfan"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"Ydych chi\'n siŵr eich bod am dderbyn pob cais i ymuno?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"Derbyn pob cais"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"Derbyn y cyfan"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Doedd dim modd derbyn pob cais. Hoffech chi roi cynnig arall arni?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Wedi methu derbyn pob cais"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"Yn derbyn pob cais i ymuno"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"Doedd dim modd derbyn y cais hwn. Hoffech chi roi cynnig arall arni?"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"Wedi methu â derbyn y cais"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"Yn derbyn cais i ymuno"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Iawn, gwrthod a gwahardd"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"Ydych chi\'n siŵr eich bod am wrthod a gwahardd %1$s? Bydd y defnyddiwr hwn ddim yn gallu gofyn am fynediad i ymuno â\'r ystafell hon eto."</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"Gwrthod a gwahardd mynediad"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"Yn gwrthod a gwahardd mynediad"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Iawn, gwrthod"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"Ydych chi\'n siŵr eich bod am wrthod %1$s cais i ymuno â\'r ystafell hon?"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"Gwrthod mynediad"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Gwrthod a gwahardd"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"Doedd dim modd i ni wrthod y cais hwn. Hoffech chi roi cynnig arall arni?"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"Wedi methu â gwrthod y cais"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"Yn gwrthod cais i ymuno"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"Pan fydd rhywun yn gofyn am gael ymuno â\'r ystafell, byddwch yn gallu gweld eu cais yma."</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"Dim cais i ymuno yn disgwyl"</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"Yn llwytho ceisiadau i ymuno…"</string>
|
||||
<string name="screen_knock_requests_list_title">"Ceisiadau i ymuno"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="zero">"Dyw %1$s na +%2$d arall eisiau ymuno â\'r ystafell hon"</item>
|
||||
<item quantity="one">"Mae %1$s +%2$d arall eisiau ymuno â\'r ystafell hon"</item>
|
||||
<item quantity="two">"Mae %1$s +%2$d arall eisiau ymuno â\'r ystafell hon"</item>
|
||||
<item quantity="few">"Mae %1$s +%2$d arall eisiau ymuno â\'r ystafell hon"</item>
|
||||
<item quantity="many">"Mae %1$s +%2$d arall eisiau ymuno â\'r ystafell hon"</item>
|
||||
<item quantity="other">"Mae %1$s +%2$d arall eisiau ymuno â\'r ystafell hon"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Gweld y cyfan"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Derbyn"</string>
|
||||
<string name="screen_room_single_knock_request_title">"Mae %1$s eisiau ymuno â\'r ystafell hon"</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"Golwg"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Ja, acceptér alle"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"Er du sikker på, at du vil acceptere alle anmodninger om at deltage?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"Acceptér alle anmodninger"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"Acceptér alle"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Vi kunne ikke acceptere alle anmodninger. Vil du prøve igen?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Kunne ikke acceptere alle anmodninger"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"Accepterer alle anmodninger om at deltage"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"Vi kunne ikke acceptere denne anmodning. Vil du prøve igen?"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"Kunne ikke acceptere anmodningen"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"Accepterer anmodning om at deltage"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Ja, afvis og spær"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"Er du sikker på, at du vil afvise og spærre %1$s? Denne bruger vil ikke kunne anmode om adgang til at deltage i dette rum igen."</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"Afvis og spær for deres adgang til rummet"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"Afviser og spærrer for adgang"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Ja, afvis"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"Er du sikker på, at du vil afvise %1$ss anmodning om at deltage i dette rum?"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"Afvis adgang"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Afvis og spær"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"Vi kunne ikke afvise denne anmodning. Vil du prøve igen?"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"Anmodningen kunne ikke afvises"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"Afviser anmodning om at deltage"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"Når nogen beder om at deltage i rummet, kan du se deres anmodning her."</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"Ingen ventende anmodning om at deltage"</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"Indlæser anmodninger om at deltage…"</string>
|
||||
<string name="screen_knock_requests_list_title">"Anmodninger om at deltage"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="one">"%1$s + %2$d anden ønsker at deltage i dette rum"</item>
|
||||
<item quantity="other">"%1$s + %2$d andre ønsker at deltage i dette rum"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Se alle"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Accepter"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s ønsker at deltage i dette rum"</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"Vis"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Ja, akzeptiere alle"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"Bist du sicher, dass du alle Beitrittsanfragen akzeptieren möchtest?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"Akzeptiere alle Beitrittsanfragen"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"Alle akzeptieren"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Wir konnten nicht alle Beitrittsanfragen annehmen. Möchtest du es noch mal versuchen?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Es konnten nicht alle Beitrittsanfragen akzeptiert werden"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"Alle Beitrittsanfragen werden angenommen"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"Wir konnten diese Beitrittsanfrage nicht annehmen. Möchtest du es noch mal versuchen?"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"Die Beitrittsanfrage konnte nicht akzeptiert werden"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"Beitrittsanfrage annehmen"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Ja, ablehnen und sperren"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"Bist du sicher, dass du %1$s ablehnen und sperren möchtest? Dieser Nutzer kann dann keinen erneuten Beitritt zu diesem Chat anfragen."</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"Ablehnen und Zugriff sperren"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"Ablehnung und Sperrung des Zugriffs"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Ja, ablehnen"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"Bist du sicher, dass du die Beitrittsanfrage von %1$s zu diesem Chat ablehnen möchtest?"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"Zugriff ablehnen"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Ablehnen und sperren"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"Wir konnten diese Beitrittsanfrage nicht ablehnen. Möchtest du es noch mal versuchen?"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"Beitrittsanfrage konnte nicht abgelehnt werden"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"Ablehnung der Beitrittsanfrage"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"Sollte jemand um Beitritt zum Chat bitten, kannst du die Anfrage hier sehen."</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"Keine ausstehende Beitrittsanfrage"</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"Beitrittsanfragen werden geladen …"</string>
|
||||
<string name="screen_knock_requests_list_title">"Beitrittsanfragen"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="one">"%1$s +%2$d weiterer möchten diesem Chat beitreten"</item>
|
||||
<item quantity="other">"%1$s +%2$d weitere möchten diesem Chat beitreten"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Alles ansehen"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Akzeptieren"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s möchte diesem Chat beitreten"</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"Ansicht"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Ναι, αποδοχή όλων"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"Σίγουρα θες να αποδεχτείς όλα τα αιτήματα συμμετοχής;"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"Αποδοχή όλων των αιτημάτων"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"Αποδοχή όλων"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Δεν μπορέσαμε να δεχτούμε όλα τα αιτήματα. Θες να προσπαθήσεις ξανά;"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Αποτυχία αποδοχής όλων των αιτημάτων"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"Αποδοχή όλων των αιτημάτων συμμετοχής"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"Δεν μπορέσαμε να δεχτούμε αυτό το αίτημα. Θα θέλατε να προσπαθήσετε ξανά;"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"Αποτυχία αποδοχής αιτήματος"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"Γίνεται αποδοχή αιτήματος συμμετοχής"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Ναι, απόρριψη και αποκλεισμός"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"Είστε σίγουροι ότι θέλετε να απορρίψετε και να αποκλείσετε το χρήστη %1$s; Αυτός ο χρήστης δεν θα μπορέσει να ζητήσει ξανά πρόσβαση για να συμμετάσχει σε αυτή την αίθουσα."</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"Απόρριψη και αποκλεισμός πρόσβασης"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"Γίνεται απόρριψη και αποκλεισμός πρόσβασης"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Ναι, απόρριψη"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"Είστε σίγουροι ότι θέλετε να απορρίψετε το αίτημα του %1$s να συμμετάσχετε σε αυτήν την αίθουσα;"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"Απόρριψη πρόσβασης"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Απόρριψη και αποκλεισμός"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"Δεν μπορέσαμε να απορρίψουμε αυτό το αίτημα. Θα θέλατε να προσπαθήσετε ξανά;"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"Απέτυχε η απόρριψη του αιτήματος"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"Γίνεται απόρριψη αιτήματος συμμετοχής"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"Όταν κάποιος θα ζητήσει να συμμετάσχει στην αίθουσα, θα μπορείτε να δείτε το αίτημά του εδώ."</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"Δεν υπάρχει εκκρεμές αίτημα συμμετοχής"</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"Φόρτωση αιτημάτων συμμετοχής…"</string>
|
||||
<string name="screen_knock_requests_list_title">"Αιτήματα συμμετοχής"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="one">"%1$s +%2$d άλλοι θέλουν να συμμετάσχουν σε αυτή την αίθουσα"</item>
|
||||
<item quantity="other">"%1$s +%2$d άλλοι θέλουν να συμμετάσχουν σε αυτή την αίθουσα"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Προβολή όλων"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Αποδοχή"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s θέλει να συμμετάσχει σε αυτή την αίθουσα"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Sí, aceptar todas"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"¿Estás seguro de que quieres aceptar todas las solicitudes de unión?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"Aceptar todas las solicitudes"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"Aceptar todas"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"No pudimos aceptar todas las solicitudes. ¿Quieres volver a intentarlo?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"No se pudieron aceptar todas las solicitudes"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"Aceptando todas las solicitudes de unión"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"No pudimos aceptar esta solicitud. ¿Quieres volver a intentarlo?"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"No se pudo aceptar la solicitud"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"Aceptando solicitud de unión"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Sí, rechazar y vetar"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"¿Estás seguro de que quieres rechazar y vetar a %1$s? Este usuario no podrá volver a solicitar acceso para unirse a esta sala."</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"Rechazar y vetar el acceso"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"Rechazando y vetando acceso"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Sí, rechazar"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"¿Estás seguro de que deseas rechazar la solicitud de %1$s para unirse a esta sala?"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"Rechazar el acceso"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Rechazar y vetar"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"No pudimos rechazar esta solicitud. ¿Quieres volver a intentarlo?"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"No se pudo rechazar la solicitud"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"Rechazando solicitud de unión"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"Cuando alguien pida unirse a la sala, podrás ver su solicitud aquí."</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"No hay ninguna solicitud de unión pendiente"</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"Cargando solicitudes de unión…"</string>
|
||||
<string name="screen_knock_requests_list_title">"Solicitudes de unión"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="one">"%1$s y %2$d más quieren unirse a esta sala"</item>
|
||||
<item quantity="other">"%1$s y otros %2$d más quieren unirse a esta sala"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Ver todo"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Aceptar"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s quiere unirse a esta sala"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Jah, võta kõik vastu"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"Kas sa oled kindel, et soovid kõik vastu liitumist soovinud võtta?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"Võta kõik vastu"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"Nõustu kõigiga"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Kõikide päringutega nõustumine polnud võimalik. Kas sa sooviksid uuesti proovida?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Kõikide liitumispalvetega nõustumine ei õnnestunud"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"Nõustume kõikide liitumispalvetega"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"Selle liitumispalvega nõustumine ei õnnestunud. Kas sa sooviksid uuesti proovida?"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"Liitumispalvega nõustumine ei õnnestunud"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"Nõustume liitumispalvega"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Jah, keeldu liitumisest ning keela ligipääs"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"Kas sa oled kindel, et soovid kasutajale %1$s keelata ligipääsu siia jututuppa ning seada talle suhtluskeelu? Seetõttu ta ei saa ka enam hiljem liitumispalvet saata."</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"Keeldu liitumisest ja keela ligipääs"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"Keeldume liitumispalvest ja seame suhtluskeelu"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Jah, keeldu"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"Kas sa oled kindel, et soovid kasutajale %1$s keelata ligipääsu siia jututuppa?"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"Keela ligipääs"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Keeldu ja määra suhtluskeeld"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"Selle liitumispalvest tagasilükkamine ei õnnestunud. Kas sa sooviksid uuesti proovida?"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"Liitumispalvest tagasilükkamine ei õnnestunud"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"Lükkame liitumispalve tagasi"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"Kui keegi soovib jututoaga liituda, siis need päringud on kuvatud siin."</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"Pole ühtegi liitumispalvet"</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"Laadime liitumispalveid…"</string>
|
||||
<string name="screen_knock_requests_list_title">"Liitumispalved"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="one">"%1$s + veel %2$d kasutaja soovivad selle jututoaga liituda"</item>
|
||||
<item quantity="other">"%1$s + veel %2$d kasutajat soovivad selle jututoaga liituda"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Vaata kõiki"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Nõustu"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s soovib selle jututoaga liituda"</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"Vaata"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Bai, onartu guztiak"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"Onartu eskaera guztiak"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"Onartu guztiak"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Bai, ukatu eta ezarri debekua"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Bai, ukatu"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"Ukatu sarbidea"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Ukatu eta ezarri debekua"</string>
|
||||
<string name="screen_knock_requests_list_title">"Sartzeko eskaerak"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="one">"%1$s + beste %2$d gelara batu nahi dute"</item>
|
||||
<item quantity="other">"%1$s + beste %2$d gelara batu nahi dute"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Ikusi guztiak"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Onartu"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s(e)k gela honetara sartu nahi du"</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"Ikusi"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"بله. پذیرش همه"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"پذیرش همهٔ درخواستها"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"پذیرش همه"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"شکست در پذیرش همهٔ درخواستها"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"پذیرفتن همهٔ درخواستهای پیوستن"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"شکست در پذیرش درخواست"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"پذیرفتن درخواست پیوستن"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"بله. رد و انسداد"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"رد و تحریم دسترسی"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"رد کردن و تحریم دسترسی"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"بله. رد شود"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"رد کردن دسترسی"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"رد و تحریم"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"شکتس در رد درخواست"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"در کردن درخواست پیوستن"</string>
|
||||
<string name="screen_knock_requests_list_title">"درخواستهای پیوستن"</string>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"دیدن همه"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"پذیرش"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s میخواهد به این اتاق بپیوندد"</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"نما"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Kyllä, hyväksy kaikki"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"Haluatko varmasti hyväksyä kaikki liittymispyynnöt?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"Hyväksy kaikki pyynnöt"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"Hyväksy kaikki"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Emme voineet hyväksyä kaikkia pyyntöjä. Haluaisitko yrittää uudelleen?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Kaikkien pyyntöjen hyväksyminen epäonnistui"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"Hyväksytään kaikkia liittymispyyntöjä"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"Emme voineet hyväksyä tätä pyyntöä. Haluaisitko yrittää uudelleen?"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"Pyynnön hyväksyminen epäonnistui"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"Hyväksytään liittymispyyntöä"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Kyllä, hylkää ja anna porttikielto"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"Haluatko varmasti hylätä käyttäjän %1$s pyynnön liittyä huoneeseen ja antaa hänelle porttikiellon? Hän ei voi enää pyytää lupaa liittyä tähän huoneeseen."</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"Hylkää ja anna porttikielto"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"Hylätään pyyntöä ja annetaan porttikieltoa"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Kyllä, hylkää"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"Haluatko varmasti hylätä käyttäjän %1$s pyynnön liittyä tähän huoneeseen?"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"Hylkää pyyntö"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Hylkää ja anna porttikielto"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"Emme voineet hylätä tätä pyyntöä. Haluaisitko yrittää uudelleen?"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"Pyynnön hylkääminen epäonnistui"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"Hylätään liittymispyyntöä"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"Kun joku pyytää liittyä huoneeseen, näet hänen pyyntönsä täällä."</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"Ei odottavia liittymispyyntöjä"</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"Ladataan liittymispyyntöjä…"</string>
|
||||
<string name="screen_knock_requests_list_title">"Liittymispyynnöt"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="one">"%1$s +%2$d muu haluavat liittyä tähän huoneeseen"</item>
|
||||
<item quantity="other">"%1$s +%2$d muuta haluavat liittyä tähän huoneeseen"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Näytä kaikki"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Hyväksy"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s haluaa liittyä tähän huoneeseen"</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"Näytä"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Oui, tout accepter"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"Êtes-vous sûr de vouloir accepter toutes les demandes pour rejoindre le salon ?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"Tout accepter"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"Tout accepter"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Toutes les demandes n’ont pas pu être acceptées. Voulez-vous réessayer ?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Toutes les demandes n’ont pas été acceptées"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"Accepter toutes les demandes à rejoindre"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"La demande n’a pas pu être acceptée. Voulez-vous réessayer ?"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"Impossible d’accepter la demande"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"Accepter la demande à rejoindre"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Oui, rejeter et bannir"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"Êtes-vous sûr de vouloir rejeter la demande et bannir %1$s ? Cet utilisateur ne pourra pas demander à nouveau à rejoindre ce salon."</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"Refuser et interdire l’accès"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"En cours de traitement…"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Oui, refuser"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"Êtes-vous sûr de vouloir refuser la demande de %1$s à rejoindre le salon ?"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"Refuser l’accès"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Refuser et bannir"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"Nous n’avons pas pu refuser cette demande. Voulez-vous réessayer ?"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"Echec"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"Traitement en cours…"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"Lorsque quelqu’un demandera à rejoindre le salon, vous pourrez voir sa demande ici."</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"Personne ne demande à rejoindre le salon"</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"Chargement…"</string>
|
||||
<string name="screen_knock_requests_list_title">"Demandes en attente"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="one">"%1$s et %2$d autre personne souhaitent rejoindre ce salon"</item>
|
||||
<item quantity="other">"%1$s et %2$d autres personnes souhaitent rejoindre ce salon"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Tout afficher"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Accepter"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s souhaite rejoindre ce salon"</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"Voir"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Igen, az összes elfogadása"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"Biztos, hogy elfogadja az összes csatlakozási kérelmet?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"Minden kérés elfogadása"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"Összes elfogadása"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Nem sikerült az összes kérés fogadása. Újra megpróbálja?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Nem sikerült az összes kérés elfogadása"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"Összes csatlakozási kérés elfogadása"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"Nem sikerült elfogadni a kérést. Megpróbálja újra?"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"Nem sikerült elfogadni a kérést"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"Csatlakozási kérés elfogadása"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Igen, elutasítás és kitiltás"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"Biztos, hogy elutasítja %1$s kérését és ki is tiltja? Többé nem fogja tudni azt kérni, hogy csatlakozhasson ehhez a szobához."</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"A hozzáférés elutasítása és kitiltás"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"A hozzáférés megtagadása és kitiltás"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Igen, elutasítás"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"Biztos, hogy elutasítja %1$s kérését, hogy csatlakozzon a szobához?"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"Hozzáférés elutasítása"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Elutasítás és kitiltás"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"Nem sikerült elutasítani a kérést. Megpróbálja újra?"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"Nem sikerült elutasítani a kérést"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"Csatlakozási kérés elutasítása"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"Ha valaki csatlakozni kíván a szobához, itt láthatja a kérését."</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"Nincs függőben lévő csatlakozási kérelem"</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"Csatlakozási kérések betöltése…"</string>
|
||||
<string name="screen_knock_requests_list_title">"Csatlakozási kérelem"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="one">"%1$s és még %2$d felhasználó szeretne csatlakozni ehhez a szobához"</item>
|
||||
<item quantity="other">"%1$s és még %2$d felhasználó szeretne csatlakozni ehhez a szobához"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Összes megtekintése"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Elfogadás"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s szeretne csatlakozni ehhez a szobához"</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"Megtekintés"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Ya, terima semua"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"Apakah Anda yakin ingin menerima semua permintaan untuk bergabung?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"Terima semua permintaan"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"Terima semua"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Kami tidak dapat menerima semua permintaan. Apakah Anda ingin mencoba lagi?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Gagal menerima semua permintaan"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"Menerima semua permintaan untuk bergabung"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"Kami tidak dapat menerima permintaan ini. Apakah Anda ingin mencoba lagi?"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"Gagal menerima permintaan"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"Menerima permintaan untuk bergabung"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Ya, tolak dan cekal"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"Apakah Anda yakin ingin menolak dan mencekal %1$s? Pengguna ini tidak akan dapat meminta akses untuk bergabung ke ruangan ini lagi."</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"Tolak dan cekal akses"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"Menolak dan mencekal akses"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Ya, tolak"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"Apakah Anda yakin ingin menolak permintaan %1$s untuk bergabung ke ruangan ini?"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"Tolak akses"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Tolak dan cekal"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"Kami tidak dapat menolak permintaan ini. Apakah Anda ingin mencoba lagi?"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"Gagal menolak permintaan"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"Menolak permintaan untuk bergabung"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"Ketika seseorang akan meminta untuk bergabung dengan ruangan, Anda akan dapat melihat permintaan mereka di sini."</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"Tidak ada permintaan yang tertunda untuk bergabung"</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"Memuat permintaan untuk bergabung…"</string>
|
||||
<string name="screen_knock_requests_list_title">"Permintaan untuk bergabung"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="other">"%1$s +%2$d lainnya ingin bergabung ke ruangan ini"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Lihat semua"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Terima"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s ingin bergabung ke ruangan ini"</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"Lihat"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Sì, accetta tutte"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"Sei sicuro di voler accettare tutte le richieste di accesso?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"Accetta tutte le richieste"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"Accetta tutte"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Non siamo riusciti ad accettare tutte le richieste. Vuoi riprovare?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Non è stato possibile accettare tutte le richieste"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"Accettazione di tutte le richieste di ingresso"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"Non è stato possibile accettare questa richiesta. Vuoi riprovare?"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"Impossibile accettare la richiesta"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"Accettazione della richiesta di ingresso"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Sì, rifiuta e blocca"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"Sei sicuro di voler rifiutare e bloccare %1$s? Questo utente non potrà richiedere nuovamente l\'accesso per entrare in questa stanza."</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"Rifiuta e blocca l\'accesso"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"Rifiuto e divieto di accesso"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Sì, rifiuta"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"Sei sicuro di voler rifiutare la richiesta di %1$s ad entrare in a questa stanza?"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"Rifiuta l\'accesso"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Rifiuta e blocca"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"Non è stato possibile rifiutare questa richiesta. Vuoi riprovare?"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"Non è stato possibile rifiutare la richiesta"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"Rifiuto della richiesta di ingresso"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"Quando qualcuno ti chiederà di entrare nella stanza, potrai vedere la sua richiesta qui."</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"Nessuna richiesta di accesso in sospeso"</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"Caricamento richieste di partecipazione…"</string>
|
||||
<string name="screen_knock_requests_list_title">"Richieste di accesso"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="one">"%1$s +%2$d vogliono entrare in questa stanza"</item>
|
||||
<item quantity="other">"%1$s +%2$d vogliono entrare in questa stanza"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Visualizza tutte"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Accetta"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s vuole entrare in questa stanza"</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"Visualizza"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"მიღება"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"네, 모두 수락합니다"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"모든 가입 요청을 정말로 수락하시겠습니까?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"모든 요청 수락"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"모두 수락"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"모든 요청을 처리할 수 없습니다. 다시 시도하시겠습니까?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"모든 요청을 수락하지 못했습니다."</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"모든 가입 요청 수락"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"이 요청을 수락할 수 없습니다. 다시 시도하시겠습니까?"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"요청을 수락하지 못했습니다"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"가입 요청 수락"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"네, 거절하고 차단합니다"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"%1$s 을 거부하고 차단하시겠습니까? 이 사용자는 이 방에 다시 참여하기 위해 액세스를 요청할 수 없습니다."</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"접근 거부 및 차단"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"접근 거부 및 차단"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"네, 거절합니다"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"%1$s 의 이 방에 대한 요청을 정말 거부하시겠습니까?"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"접근 거부"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"거부 및 차단"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"이 요청을 거부할 수 없습니다. 다시 시도하시겠습니까?"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"요청 거부에 실패했습니다"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"가입 요청 거부"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"누군가가 방에 참여 요청을 한다면, 여기에서 그 요청을 볼 수 있습니다."</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"보류 중인 가입 요청이 없습니다."</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"가입 요청을 로딩 중…"</string>
|
||||
<string name="screen_knock_requests_list_title">"참여 요청"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="other">"%1$s +%2$d 명이 이 방에 참여하고 싶어합니다."</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"모두 보기"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"수락"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s 이 방에 참여하고 싶습니다."</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"보기"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Priimti"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Ja, godta alle"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"Er du sikker på at du vil godta alle forespørsler om å bli med?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"Godta alle forespørsler"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"Godta alle"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Vi kunne ikke godta alle forespørsler. Vil du prøve igjen?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Kunne ikke godta alle forespørsler"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"Godtar alle forespørsler om å bli med"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"Vi kunne ikke godta denne forespørselen. Vil du prøve igjen?"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"Kunne ikke godta forespørselen"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"Godtar forespørsel om å bli med"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Ja, avslå og utesteng"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"Er du sikker på at du vil avvise og utestenge %1$s? Denne brukeren vil ikke kunne be om tilgang til dette rommet igjen."</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"Avslå og forby tilgang"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"Avslår og forbyr tilgang"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Ja, avslå"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"Er du sikker på at du vil avslå %1$ss forespørsel om å bli med i dette rommet?"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"Avslå tilgang"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Avslå og forby"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"Vi kunne ikke avslå denne forespørselen. Vil du prøve igjen?"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"Kunne ikke avslå forespørselen"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"Avslår forespørsel om å bli med"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"Når noen ber om å bli med i rommet, vil du kunne se forespørselen deres her."</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"Ingen ventende forespørsel om å bli med"</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"Laster inn forespørsler om å bli med…"</string>
|
||||
<string name="screen_knock_requests_list_title">"Forespørsler om å bli med"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="one">"%1$s +%2$d andre ønsker å bli med i dette rommet"</item>
|
||||
<item quantity="other">"%1$s +%2$d andre ønsker å bli med i dette rommet"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Vis alle"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Godta"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s ønsker å bli med i dette rommet"</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"Vis"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Accepteren"</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"Bekijken"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Tak, akceptuj wszystkie"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"Czy na pewno chcesz zaakceptować wszystkie prośby o dołączenie?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"Akceptuj wszystkie prośby"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"Akceptuj wszystkie"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Nie udało się zaakceptować wszystkich próśb. Czy chcesz spróbować ponownie?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Nie udało się zaakceptować wszystkich próśb"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"Akceptowanie wszystkich próśb o dołączenie"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"Nie udało się zaakceptować prośby. Czy chcesz spróbować ponownie?"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"Nie udało się zaakceptować prośby"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"Akceptuję prośbę o dołączenie"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Tak, odrzuć i zbanuj"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"Czy na pewno chcesz odrzucić i zbanować %1$s? Użytkownik nie będzie mógł ponownie poprosić o dostęp do tego pokoju."</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"Odrzuć i zbanuj dostęp"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"Odrzucanie i banowanie dostępu"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Tak, odrzuć"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"Czy na pewno chcesz odrzucić %1$s prośbę o dołączenie do tego pokoju?"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"Odrzuć dostęp"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Odrzuć i zbanuj"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"Nie udało się odrzucić prośby. Czy chcesz spróbować ponownie?"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"Nie udało się odrzucić prośby"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"Odrzucanie prośby o dołączenie"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"Kiedy ktoś poprosi o dołączenie do pokoju, będziesz mógł zobaczyć jego prośbę tutaj."</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"Brak oczekujących próśb o dołączenie"</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"Wczytywanie próśb o dołączenie…"</string>
|
||||
<string name="screen_knock_requests_list_title">"Prośby o dołączenie"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="one">"%1$s +%2$d inny chce dołączyć do pokoju"</item>
|
||||
<item quantity="few">"%1$s +%2$d innych chce dołączyć do pokoju"</item>
|
||||
<item quantity="many">"%1$s +%2$d innych chce dołączyć do pokoju"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Wyświetl wszystkie"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Akceptuj"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s chce dołączyć do tego pokoju"</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"Wyświetl"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Sim, aceitar todos"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"Tem certeza de que deseja aceitar todos os pedidos de entrada?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"Aceitar todos os pedidos"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"Aceitar todos"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Não pudemos aceitar todas as solicitações. Você gostaria de tentar novamente?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Falha ao aceitar todas as solicitações"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"Aceitando todas as solicitações de entrada"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"Não pudemos aceitar essa solicitação. Você gostaria de tentar novamente?"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"Falha ao aceitar a solicitação"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"Aceitando solicitação de entrada"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Sim, recusar e banir"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"Você tem certeza de que deseja recusar e banir %1$s? Este usuário não poderá solicitar acesso para entrar nesta sala novamente."</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"Recusar e proibir o acesso"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"Recusando e proibindo o acesso"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Sim, recusar"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"Você tem certeza de que deseja recusar a solicitação de %1$s para entrar nesta sala?"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"Recusar acesso"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Recusar e banir"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"Não foi possível recusar esta solicitação. Você gostaria de tentar novamente?"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"Falha ao recusar a solicitação"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"Recusando a solicitação de entrada"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"Quando alguém pedir para entrar na sala, você poderá ver o pedido aqui."</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"Nenhum pedido de entrada pendente"</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"Carregando solicitações de entrada…"</string>
|
||||
<string name="screen_knock_requests_list_title">"Pedidos de entrada"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="one">"%1$s + outro %2$d desejam entrar nesta sala"</item>
|
||||
<item quantity="other">"%1$s + outros %2$d desejam entrar nesta sala"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Ver tudo"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Aceitar"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s quer entrar nesta sala"</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"Visualizar"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Sim, aceitar todos"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"Tens a certeza de que queres aceitar todos os pedidos de entrada?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"Aceitar todos os pedidos"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"Aceitar todos"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Não foi possível aceitar todos os pedidos. Queres tentar novamente?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Falha ao aceitar todos os pedidos"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"A aceitar todos os pedidos de entrada"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"Não foi possível aceitar este pedido. Queres tentar novamente?"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"Falha ao aceitar pedido"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"A aceitar pedido de entrada"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Sim, recusar e proibir"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"Tens a certeza de que queres recusar e banir %1$s? Este utilizador não poderá voltar a pedir para entrar nesta sala."</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"Recusar e banir"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"A rejeitar pedido e a banir o utilizador"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Sim, rejeitar"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"Tens a certeza que queres recusar o pedido de entrada de %1$s?"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"Rejeitar entrada"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Recusar e banir"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"Não foi possível rejeitar este pedido. Queres tentar novamente?"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"Falha ao rejeitar pedido"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"A rejeitar pedido de entrada"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"Quando alguém pedir para entrar na sala, irás poder rever o pedido aqui."</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"Sem pedidos de entrada"</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"A carregar pedidos de entrada…"</string>
|
||||
<string name="screen_knock_requests_list_title">"Pedidos de entrada"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="one">"%1$s +%2$d outro querem entrar nesta sala"</item>
|
||||
<item quantity="other">"%1$s +%2$d outros querem entrar nesta sala"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Ver todos"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Aceitar"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s quer entrar nesta sala"</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"Ver"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Da, acceptati tot"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"Sunteți sigur că doriți să acceptați toate cererile de alăturare?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"Acceptați toate cererile"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"Acceptați tot"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Nu am putut accepta toate cererile. Doriți să încercați din nou?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Nu s-au putut accepta toate cererile"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"Se acceptă toate cererile de alăturare"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"Nu am putut accepta această cerere. Doriți să încercați din nou?"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"Nu s-a putut accepta cererea"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"Se acceptă cererea de alăturare"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Da, refuzați și interziceți"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"Sunteți sigur că doriți să refuzați și să interziceți accesul lui %1$s? Acest utilizator nu va mai putea cere accesul pentru a se alătura acestei camere."</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"Refuzați și interziceți accesul"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"Se refuză și interzice accesul"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Da, refuzați"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"Sunteți sigur că doriți să refuzați cererea %1$s de a vă alătura acestei camere?"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"Refuzați accesul"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Refuzați și interziceți"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"Nu am putut refuza această cerere. Doriți să încercați din nou?"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"Cererea nu a putut fi respinsă"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"Se refuza cererea de alăturare"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"Când cineva va cere să se alăture camerei, veți putea vedea cererea aici."</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"Nu există cereri de alăturare în așteptare"</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"Se încarcă cererile de alăturare…"</string>
|
||||
<string name="screen_knock_requests_list_title">"Cereri de alăturare"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="one">"%1$s +%2$d utilizator doresc să se alăture acestei camere"</item>
|
||||
<item quantity="few">"%1$s +%2$d utilizatori doresc să se alăture acestei camere"</item>
|
||||
<item quantity="other">"%1$s +%2$d utilizatori doresc să se alăture acestei camere"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Vizualizați tot"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Acceptați"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s dorește să se alăture acestei camere"</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"Vizualizați"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Да, принять все"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"Вы действительно хотите принять все заявки на присоединение?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"Принять все запросы"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"Принять всё"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Мы не смогли принять все запросы. Хотите попробовать еще раз?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Не удалось принять все запросы"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"Принять все заявки на присоединение"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"Мы не смогли принять этот запрос. Хотите попробовать еще раз?"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"Не удалось принять запрос"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"Принятие заявки на присоединение"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Да, отклонить и запретить"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"Вы уверены, что хотите отклонить и запретить %1$s? Этот пользователь больше не сможет запросить доступ к этой комнате."</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"Отклонить и запретить доступ"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"Отклонение и запрет доступа"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Да, отклонить"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"Вы уверены, что хотите отклонить %1$s запрос на присоединение к этой комнате?"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"Отклонить доступ"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Отклонить и запретить"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"Мы не смогли отклонить этот запрос. Хотите попробовать еще раз?"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"Не удалось отклонить запрос"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"Отклонение заявки на присоединение"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"Вы сможете увидеть запрос, когда кто-то попросит присоединиться к комнате."</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"Нет ожидающих запросов на присоединение"</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"Загрузка запросов на присоединение…"</string>
|
||||
<string name="screen_knock_requests_list_title">"Запросы на вступление"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="one">"%1$s +%2$d хочет присоединиться к этой комнате"</item>
|
||||
<item quantity="few">"%1$s +%2$d хотят присоединиться к этой комнате"</item>
|
||||
<item quantity="many">"%1$s +%2$d хотят присоединиться к этой комнате"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Показать все"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Разрешить"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s хочет присоединиться к этой комнате"</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"Просмотр"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Áno, prijať všetky"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"Ste si istí, že chcete prijať všetky žiadosti o pripojenie?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"Prijať všetky žiadosti"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"Prijať všetky"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Nepodarilo sa prijať všetky žiadosti. Chceli by ste to skúsiť znova?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Nepodarilo sa prijať všetky žiadosti"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"Prijímanie všetkých žiadostí o pripojenie"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"Túto žiadosť sme nemohli prijať. Chceli by ste to skúsiť znova?"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"Žiadosť sa nepodarilo prijať"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"Prijíma sa žiadosť o pripojenie"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Áno, odmietnuť a zakázať"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"Ste si istí, že chcete odmietnuť a zakázať používateľa %1$s? Tento používateľ nebude môcť znova požiadať o prístup k tejto miestnosti."</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"Odmietnuť a zakázať prístup"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"Odmietnutie a zákaz prístupu"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Áno, odmietnuť"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"Ste si istí, že chcete odmietnuť používateľovi %1$s žiadosť o vstup do tejto miestnosti?"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"Odmietnuť prístup"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Odmietnuť a zakázať"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"Túto žiadosť sa nepodarilo odmietnuť. Chceli by ste to skúsiť znova?"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"Nepodarilo sa odmietnuť žiadosť"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"Odmietnutie žiadosti o prihlásenie"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"Keď niekto požiada, aby sa pripojil k miestnosti, jeho žiadosť si môžete pozrieť tu."</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"Žiadna čakajúca žiadosť o pripojenie"</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"Načítavajú sa žiadosti o pripojenie…"</string>
|
||||
<string name="screen_knock_requests_list_title">"Žiadosti o vstup"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="one">"%1$s +%2$d ďalší chcú vstúpiť do tejto miestnosti"</item>
|
||||
<item quantity="few">"%1$s +%2$d ďalší chcú vstúpiť do tejto miestnosti"</item>
|
||||
<item quantity="other">"%1$s +%2$d ďalších chce vstúpiť do tejto miestnosti"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Zobraziť všetko"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Prijať"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s chce vstúpiť do tejto miestnosti"</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"Zobraziť"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Ja, acceptera alla"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"Är du säker på att du vill acceptera alla förfrågningar om att gå med?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"Acceptera alla förfrågningar"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"Acceptera alla"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Vi kunde inte acceptera alla förfrågningar. Vill du försöka igen?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Misslyckades att acceptera alla förfrågningar"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"Accepterar alla förfrågningar om att gå med"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"Vi kunde inte acceptera denna begäran. Vill du försöka igen?"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"Misslyckades att acceptera förfrågan"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"Accepterar begäran om att gå med"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Ja, avslå och förbjud"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"Är du säker på att du vill avvisa och förbjuda%1$s? Den här användaren kommer inte att kunna begära åtkomst för att gå med i det här rummet igen."</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"Avvisa och förbjud åtkomst"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"Avvisar och bannar åtkomst"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Ja, avböj"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"Är du säker på att du vill avslå %1$s begäran om att gå med i det här rummet?"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"Avvisa åtkomst"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Avvisa och förbjud"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"Vi kunde inte avslå denna begäran. Vill du försöka igen?"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"Misslyckades att avvisa begäran"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"Avvisa begäran om att gå med"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"När någon begär om att gå med i rummet, kan du se deras förfrågan här."</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"Ingen väntande begäran om att gå med"</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"Laddar förfrågningar om att gå med …"</string>
|
||||
<string name="screen_knock_requests_list_title">"Begäran om att gå med"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="one">"%1$s+ %2$d annan vill gå med i detta rum"</item>
|
||||
<item quantity="other">"%1$s+ %2$d andra vill gå med i detta rum"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Visa alla"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Godkänn"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s vill gå med i det här rummet"</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"Visa"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Evet, tümünü kabul et"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"Tüm katılma isteklerini kabul etmek istediğinizden emin misiniz?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"Tüm istekleri kabul et"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"Tümünü kabul et"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Tüm istekleri kabul edemedik. Tekrar denemek ister misiniz?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Tüm istekler kabul edilemedi"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"Tüm katılım istekleri kabul ediliyor"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"Bu isteği kabul edemedik. Tekrar denemek ister misiniz?"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"İstek kabul edilemedi"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"Katılma isteği kabul ediliyor"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Evet, reddet ve yasakla"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"%1$s reddetmek ve yasaklamak istediğinizden emin misiniz? Bu kullanıcı, bu odaya tekrar katılmak için erişim isteğinde bulunamaz."</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"Reddet ve erişimi yasakla"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"Reddediliyor ve erişim yasaklanıyor"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Evet, reddet"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"Bu odaya katılma isteğini reddetmek istediğinizden emin misiniz %1$s?"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"Erişimi reddet"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Reddet ve yasakla"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"Bu isteği reddedemedik. Tekrar denemek ister misiniz?"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"İsteği reddetme başarısız oldu"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"Katılma isteği reddediliyor"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"Birisi odaya katılmak istediğinde, isteklerini burada görebileceksiniz."</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"Bekleyen katılım isteği yok"</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"Katılma istekleri yükleniyor…"</string>
|
||||
<string name="screen_knock_requests_list_title">"Katılma istekleri"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="one">"%1$s +%2$d kişi daha bu odaya katılmak istiyor"</item>
|
||||
<item quantity="other">"%1$s +%2$d kişi daha bu odaya katılmak istiyor"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Tümünü görüntüle"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Kabul et"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s bu odaya katılmak istiyor"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Так, прийняти всі"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"Ви впевнені, що хочете прийняти всі запити на приєднання?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"Прийняти всі запити"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"Прийняти всі"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Ми не змогли прийняти всі запити. Бажаєте спробувати ще раз?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Не вдалося прийняти всі запити"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"Прийняття всіх запитів на приєднання"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"Ми не змогли прийняти цей запит. Бажаєте спробувати ще раз?"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"Не вдалося прийняти запит"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"Прийняття запиту на приєднання"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Так, відхилити та заблокувати"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"Ви впевнені, що хочете відхилити та заборонити %1$s? Цей користувач не зможе знову запитувати про доступ до цієї кімнати."</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"Відмова та заборона доступу"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"Відмова та заборона доступу"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Так, відхилити"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"Ви впевнені, що хочете відхилити запит %1$s на приєднання до цієї кімнати?"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"Відмовити в доступі"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Відхилити та заблокувати"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"Ми не змогли відхилити цей запит. Бажаєте спробувати ще раз?"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"Не вдалося відхилити запит"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"Відхилення запиту на приєднання"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"Коли хтось попросить приєднатися до кімнати, ви зможете побачити їхній запит тут."</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"Немає нерозглянутих запитів на приєднання"</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"Завантаження запитів на приєднання…"</string>
|
||||
<string name="screen_knock_requests_list_title">"Запити на приєднання"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="one">"%1$s +%2$d інший хочуть приєднатися до цієї кімнати"</item>
|
||||
<item quantity="few">"%1$s +%2$d інші хочуть приєднатися до цієї кімнати"</item>
|
||||
<item quantity="many">"%1$s +%2$d інших хочуть приєднатися до цієї кімнати"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Переглянути все"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Прийняти"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s хоче приєднатися до цієї кімнати"</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"Переглянути"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"قبول کریں"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Ha, hammasini qabul qiling"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"Barcha qo‘shilish so‘rovlarini qabul qilishga ishonchingiz komilmi?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"Barcha so‘rovlarni qabul qilish"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"Hammasini qabul qiling"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"Biz barcha so‘rovlarni qabul qila olmadik. Qayta urinib koʻrmoqchimisiz?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Barcha so‘rovlar qabul qilinmadi"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"Qo‘shilish so‘rovi qabul qilinmoqda"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"Biz bu so‘rovni qabul qila olmadik. Yana bir bor urinib ko‘rishni xohlaysizmi?"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"So‘rovni qabul qilib bo‘lmadi"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"Qo‘shilish so‘rovi qabul qilinmoqda"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Ha, rad eting va taqiqlang"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"Siz %1$sʼni rad etib, taqiqlashni xohlayotganingizga ishonchingiz komilmi? Bu foydalanuvchi ushbu xonaga qayta kirish uchun ruxsat so‘ray olmaydi."</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"Rad etish va kirishni taqiqlash"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"Kirishni rad etish va taqiqlash"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Ha, rad etish"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"%1$sning bu xonaga qo‘shilish so‘rovini rad etasizmi?"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"Kirishni rad etish"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Rad etish va taqiqlash"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"Biz bu iltimosni rad etolmasdik. Yana bir bor urinib ko‘rishni xohlaysizmi?"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"So‘rovni rad etib bo‘lmadi"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"Qo‘shilish so‘rovi rad etilayapti"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"Kimdir xonaga qo‘shilishni so‘raganda, uning iltimosini shu yerda ko‘rishingiz mumkin."</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"Qo‘shilish so‘rovi kutilmayapti"</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"Qo‘shilish uchun so‘rovlar yuklanmoqda…"</string>
|
||||
<string name="screen_knock_requests_list_title">"Qo‘shilish uchun so‘rovlar"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="one">"%1$s + %2$d kishi bu xonaga qo‘shilmoqchi"</item>
|
||||
<item quantity="other">"%1$s + %2$d kishi bu xonaga qo‘shilmoqchi"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"Hammasini ko\'rish"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Qabul qiling"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s bu xonaga qo‘shilmoqchi"</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"Ko\'rish"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"是的,全部接受"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"您確定要接受所有加入請求嗎?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"接受所有請求"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"全部接受"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"我們無法接受所有請求。您想要再試一次嗎?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"無法接受所有請求"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"接受所有加入請求"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"我們無法接受此請求。您想要再試一次嗎?"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"無法接受請求"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"接受加入請求"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"是的,拒絕並封鎖"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"您確定要拒絕並封鎖 %1$s 嗎?此使用者將無法再次申請加入此聊天室。"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"拒絕並禁止存取"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"拒絕並封鎖存取權"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"是的,拒絕"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"您確定您要拒絕 %1$s 加入此聊天室的請求嗎?"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"拒絕存取"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"拒絕並封鎖"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"我們無法拒絕此請求。您想要再試一次嗎?"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"拒絕請求失敗"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"拒絕加入請求"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"當有人要求加入聊天室時,您可以在這裡看到他們的請求。"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"沒有待處理的加入請求"</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"正在載入加入請求……"</string>
|
||||
<string name="screen_knock_requests_list_title">"請求加入"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="other">"%1$s 與 %2$d 個其他人想要加入此聊天室"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"檢視全部"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"接受"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s 想要加入此聊天室"</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"檢視"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"是的,全部接受"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"您确定要接受所有加入请求吗?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"接受所有请求"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"全部接受"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"我们无法接受所有请求。是否要再试一次?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"无法接受所有请求"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"接受所有加入请求"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"我们无法接受此请求。是否要再试一次?"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"无法接受请求"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"接受加入请求"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"是的,拒绝并禁止"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"您确定要拒绝并禁止吗%1$s?该用户将无法再次请求加入该房间。"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"拒绝并禁止访问"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"拒绝并禁止访问"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"是的,拒绝"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"您确定要拒绝 %1$s 加入此房间的请求吗?"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"拒绝访问"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"拒绝和禁止"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"我们无法拒绝此请求。是否要再试一次?"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"拒绝请求失败"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"拒绝加入请求"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"当有人请求加入房间时,您将能够在这里看到他们的请求。"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"没有待处理的加入请求"</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"正在加载加入请求…"</string>
|
||||
<string name="screen_knock_requests_list_title">"申请加入"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="other">"%1$s+ %2$d 其他人想加入这个房间"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"查看全部"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"接受"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s想加入这个房间"</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"查看"</string>
|
||||
</resources>
|
||||
36
features/knockrequests/impl/src/main/res/values/localazy.xml
Normal file
36
features/knockrequests/impl/src/main/res/values/localazy.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_knock_requests_list_accept_all_alert_confirm_button_title">"Yes, accept all"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_description">"Are you sure you want to accept all requests to join?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_alert_title">"Accept all requests"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_button_title">"Accept all"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_description">"We couldn’t accept all requests. Would you like to try again?"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_failed_alert_title">"Failed to accept all requests"</string>
|
||||
<string name="screen_knock_requests_list_accept_all_loading_title">"Accepting all requests to join"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_description">"We couldn’t accept this request. Would you like to try again?"</string>
|
||||
<string name="screen_knock_requests_list_accept_failed_alert_title">"Failed to accept request"</string>
|
||||
<string name="screen_knock_requests_list_accept_loading_title">"Accepting request to join"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_confirm_button_title">"Yes, decline and ban"</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_description">"Are you sure you want to decline and ban %1$s? This user won’t be able to request access to join this room again."</string>
|
||||
<string name="screen_knock_requests_list_ban_alert_title">"Decline and ban from accessing"</string>
|
||||
<string name="screen_knock_requests_list_ban_loading_title">"Declining and banning access"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_confirm_button_title">"Yes, decline"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_description">"Are you sure you want to decline %1$s request to join this room?"</string>
|
||||
<string name="screen_knock_requests_list_decline_alert_title">"Decline access"</string>
|
||||
<string name="screen_knock_requests_list_decline_and_ban_action_title">"Decline and ban"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_description">"We couldn’t decline this request. Would you like to try again?"</string>
|
||||
<string name="screen_knock_requests_list_decline_failed_alert_title">"Failed to decline request"</string>
|
||||
<string name="screen_knock_requests_list_decline_loading_title">"Declining request to join"</string>
|
||||
<string name="screen_knock_requests_list_empty_state_description">"When somebody will ask to join the room, you’ll be able to see their request here."</string>
|
||||
<string name="screen_knock_requests_list_empty_state_title">"No pending request to join"</string>
|
||||
<string name="screen_knock_requests_list_initial_loading_title">"Loading requests to join…"</string>
|
||||
<string name="screen_knock_requests_list_title">"Requests to join"</string>
|
||||
<plurals name="screen_room_multiple_knock_requests_title">
|
||||
<item quantity="one">"%1$s +%2$d other want to join this room"</item>
|
||||
<item quantity="other">"%1$s +%2$d others want to join this room"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_multiple_knock_requests_view_all_button_title">"View all"</string>
|
||||
<string name="screen_room_single_knock_request_accept_button_title">"Accept"</string>
|
||||
<string name="screen_room_single_knock_request_title">"%1$s wants to join this room"</string>
|
||||
<string name="screen_room_single_knock_request_view_button_title">"View"</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.impl.banner
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.knockrequests.impl.data.KnockRequestPermissions
|
||||
import io.element.android.features.knockrequests.impl.data.KnockRequestsService
|
||||
import io.element.android.libraries.matrix.api.room.knock.KnockRequest
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID_2
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID_3
|
||||
import io.element.android.libraries.matrix.test.room.knock.FakeKnockRequest
|
||||
import io.element.android.tests.testutils.lambda.assert
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.test
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.advanceUntilIdle
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class) class KnockRequestsBannerPresenterTest {
|
||||
@Test
|
||||
fun `present - when feature is disabled then the banner should be hidden`() = runTest {
|
||||
val knockRequests = flowOf(listOf(FakeKnockRequest()))
|
||||
val presenter = createKnockRequestsBannerPresenter(isFeatureEnabled = false, knockRequestsFlow = knockRequests)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.isVisible).isFalse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when empty knock request list then the banner should be hidden`() = runTest {
|
||||
val knockRequests = flowOf(emptyList<KnockRequest>())
|
||||
val presenter = createKnockRequestsBannerPresenter(knockRequestsFlow = knockRequests)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.isVisible).isFalse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when no permission to manage knock requests then the banner should be hidden`() = runTest {
|
||||
val presenter = createKnockRequestsBannerPresenter(canAcceptKnockRequests = false)
|
||||
presenter.test {
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.isVisible).isFalse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when everything is setup to manage knocks with data, then the banner should be visible`() = runTest {
|
||||
val knockRequests = flowOf(
|
||||
listOf(
|
||||
FakeKnockRequest(
|
||||
reason = "A reason",
|
||||
)
|
||||
)
|
||||
)
|
||||
val presenter = createKnockRequestsBannerPresenter(knockRequestsFlow = knockRequests)
|
||||
presenter.test {
|
||||
skipItems(2)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.isVisible).isTrue()
|
||||
assertThat(state.knockRequests).hasSize(1)
|
||||
assertThat(state.canAccept).isTrue()
|
||||
assertThat(state.reason).isEqualTo("A reason")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when multiple knock requests, the banner should not have reason nor subtitle`() = runTest {
|
||||
val knockRequests = flowOf(
|
||||
listOf(
|
||||
FakeKnockRequest(
|
||||
displayName = "Alice",
|
||||
),
|
||||
FakeKnockRequest(
|
||||
displayName = "Bob",
|
||||
),
|
||||
FakeKnockRequest(
|
||||
displayName = "Charlie",
|
||||
),
|
||||
)
|
||||
)
|
||||
val presenter = createKnockRequestsBannerPresenter(knockRequestsFlow = knockRequests)
|
||||
presenter.test {
|
||||
skipItems(2)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.isVisible).isTrue()
|
||||
assertThat(state.knockRequests).hasSize(3)
|
||||
assertThat(state.reason).isNull()
|
||||
assertThat(state.subtitle).isNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when there are some seen knock requests, then the banner should filtered them`() = runTest {
|
||||
val knockRequests = flowOf(
|
||||
listOf(
|
||||
FakeKnockRequest(
|
||||
displayName = "Alice",
|
||||
isSeen = true,
|
||||
userId = A_USER_ID
|
||||
),
|
||||
FakeKnockRequest(
|
||||
displayName = "Bob",
|
||||
isSeen = true,
|
||||
userId = A_USER_ID_2
|
||||
),
|
||||
FakeKnockRequest(
|
||||
isSeen = false,
|
||||
displayName = "Charlie",
|
||||
reason = "A reason",
|
||||
userId = A_USER_ID_3
|
||||
),
|
||||
)
|
||||
)
|
||||
val presenter = createKnockRequestsBannerPresenter(knockRequestsFlow = knockRequests)
|
||||
presenter.test {
|
||||
skipItems(2)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.isVisible).isTrue()
|
||||
// Only Charlie should be displayed
|
||||
assertThat(state.knockRequests).hasSize(1)
|
||||
assertThat(state.reason).isEqualTo("A reason")
|
||||
assertThat(state.subtitle).isEqualTo(A_USER_ID_3.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - given AcceptSingleRequest event with failure, then the banner should hide and reappear and error should appear and disappear`() = runTest {
|
||||
val acceptLambda = lambdaRecorder<Result<Unit>> { Result.failure(Exception()) }
|
||||
val knockRequest = FakeKnockRequest(
|
||||
displayName = "Alice",
|
||||
reason = "A reason",
|
||||
acceptLambda = acceptLambda
|
||||
)
|
||||
val knockRequests = flowOf(listOf(knockRequest))
|
||||
val presenter = createKnockRequestsBannerPresenter(knockRequestsFlow = knockRequests)
|
||||
presenter.test {
|
||||
skipItems(2)
|
||||
awaitItem().also { state ->
|
||||
state.eventSink(KnockRequestsBannerEvents.AcceptSingleRequest)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.isVisible).isFalse()
|
||||
assertThat(state.displayAcceptError).isFalse()
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.isVisible).isFalse()
|
||||
assertThat(state.displayAcceptError).isTrue()
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.isVisible).isTrue()
|
||||
assertThat(state.displayAcceptError).isTrue()
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.isVisible).isTrue()
|
||||
assertThat(state.displayAcceptError).isFalse()
|
||||
}
|
||||
assert(acceptLambda).isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - given an AcceptSingleRequest event with success, then banner should be dismissed`() = runTest {
|
||||
val acceptLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
|
||||
val knockRequest = FakeKnockRequest(
|
||||
displayName = "Alice",
|
||||
reason = "A reason",
|
||||
acceptLambda = acceptLambda
|
||||
)
|
||||
val knockRequests = flowOf(listOf(knockRequest))
|
||||
val presenter = createKnockRequestsBannerPresenter(knockRequestsFlow = knockRequests)
|
||||
presenter.test {
|
||||
skipItems(2)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.knockRequests).hasSize(1)
|
||||
state.eventSink(KnockRequestsBannerEvents.AcceptSingleRequest)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.isVisible).isFalse()
|
||||
}
|
||||
advanceUntilIdle()
|
||||
assert(acceptLambda).isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - given a Dismiss event, then knock requests should be marked as seen`() = runTest {
|
||||
val markAsSeenLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
|
||||
val knockRequests = flowOf(
|
||||
listOf(
|
||||
FakeKnockRequest(markAsSeenLambda = markAsSeenLambda),
|
||||
FakeKnockRequest(markAsSeenLambda = markAsSeenLambda),
|
||||
FakeKnockRequest(markAsSeenLambda = markAsSeenLambda),
|
||||
)
|
||||
)
|
||||
val presenter = createKnockRequestsBannerPresenter(knockRequestsFlow = knockRequests)
|
||||
presenter.test {
|
||||
skipItems(2)
|
||||
awaitItem().also { state ->
|
||||
state.eventSink(KnockRequestsBannerEvents.Dismiss)
|
||||
}
|
||||
advanceUntilIdle()
|
||||
assert(markAsSeenLambda).isCalledExactly(3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun TestScope.createKnockRequestsBannerPresenter(
|
||||
knockRequestsFlow: Flow<List<KnockRequest>> = flowOf(emptyList()),
|
||||
canAcceptKnockRequests: Boolean = true,
|
||||
isFeatureEnabled: Boolean = true,
|
||||
): KnockRequestsBannerPresenter {
|
||||
val knockRequestsService = KnockRequestsService(
|
||||
knockRequestsFlow = knockRequestsFlow,
|
||||
coroutineScope = backgroundScope,
|
||||
isKnockFeatureEnabledFlow = flowOf(isFeatureEnabled),
|
||||
permissionsFlow = flowOf(KnockRequestPermissions(canAcceptKnockRequests, canAcceptKnockRequests, canAcceptKnockRequests)),
|
||||
)
|
||||
return KnockRequestsBannerPresenter(
|
||||
knockRequestsService = knockRequestsService,
|
||||
sessionCoroutineScope = this,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.impl.banner
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.features.knockrequests.impl.R
|
||||
import io.element.android.features.knockrequests.impl.data.aKnockRequestPresentable
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.clickOn
|
||||
import io.element.android.tests.testutils.ensureCalledOnce
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class KnockRequestsBannerViewTest {
|
||||
@get:Rule
|
||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
@Test
|
||||
fun `clicking on view on single request invoke the expected callback`() {
|
||||
val eventsRecorder = EventsRecorder<KnockRequestsBannerEvents>(expectEvents = false)
|
||||
ensureCalledOnce {
|
||||
rule.setKnockRequestsBannerView(
|
||||
state = aKnockRequestsBannerState(
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
onViewRequestsClick = it
|
||||
)
|
||||
rule.clickOn(R.string.screen_room_single_knock_request_view_button_title)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on view all when multiple requests invoke the expected callback`() {
|
||||
val eventsRecorder = EventsRecorder<KnockRequestsBannerEvents>(expectEvents = false)
|
||||
ensureCalledOnce {
|
||||
rule.setKnockRequestsBannerView(
|
||||
state = aKnockRequestsBannerState(
|
||||
knockRequests = listOf(
|
||||
aKnockRequestPresentable(displayName = "Alice"),
|
||||
aKnockRequestPresentable(displayName = "Bob"),
|
||||
aKnockRequestPresentable(displayName = "Charlie")
|
||||
),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
onViewRequestsClick = it
|
||||
)
|
||||
rule.clickOn(R.string.screen_room_multiple_knock_requests_view_all_button_title)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on accept on a single request emit the expected event`() {
|
||||
val eventsRecorder = EventsRecorder<KnockRequestsBannerEvents>()
|
||||
rule.setKnockRequestsBannerView(
|
||||
state = aKnockRequestsBannerState(
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_accept)
|
||||
eventsRecorder.assertSingle(KnockRequestsBannerEvents.AcceptSingleRequest)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on dismiss emit the expected event`() {
|
||||
val eventsRecorder = EventsRecorder<KnockRequestsBannerEvents>()
|
||||
rule.setKnockRequestsBannerView(
|
||||
state = aKnockRequestsBannerState(
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
val close = rule.activity.getString(CommonStrings.action_close)
|
||||
rule.onNodeWithContentDescription(close).performClick()
|
||||
eventsRecorder.assertSingle(KnockRequestsBannerEvents.Dismiss)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setKnockRequestsBannerView(
|
||||
state: KnockRequestsBannerState,
|
||||
onViewRequestsClick: () -> Unit = EnsureNeverCalled(),
|
||||
) {
|
||||
setContent {
|
||||
KnockRequestsBannerView(
|
||||
state = state,
|
||||
onViewRequestsClick = onViewRequestsClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.impl.list
|
||||
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.tests.testutils.node.TestParentNode
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultKnockRequestsListEntryPointTest {
|
||||
@get:Rule
|
||||
val instantTaskExecutorRule = InstantTaskExecutorRule()
|
||||
|
||||
@Test
|
||||
fun `test node builder`() = runTest {
|
||||
val entryPoint = DefaultKnockRequestsListEntryPoint()
|
||||
|
||||
val parentNode = TestParentNode.create { buildContext, plugins ->
|
||||
KnockRequestsListNode(
|
||||
buildContext = buildContext,
|
||||
plugins = plugins,
|
||||
presenter = createKnockRequestsListPresenter(),
|
||||
)
|
||||
}
|
||||
val result = entryPoint.createNode(parentNode, BuildContext.root(null))
|
||||
assertThat(result).isInstanceOf(KnockRequestsListNode::class.java)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
package io.element.android.features.knockrequests.impl.list
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.knockrequests.impl.data.KnockRequestPermissions
|
||||
import io.element.android.features.knockrequests.impl.data.KnockRequestsService
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.api.room.knock.KnockRequest
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID_2
|
||||
import io.element.android.libraries.matrix.test.room.knock.FakeKnockRequest
|
||||
import io.element.android.tests.testutils.lambda.assert
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.test
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class KnockRequestsListPresenterTest {
|
||||
@Test
|
||||
fun `present - initial states should be emitted`() = runTest {
|
||||
val presenter = createKnockRequestsListPresenter()
|
||||
presenter.test {
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.knockRequests).isInstanceOf(AsyncData.Loading::class.java)
|
||||
assertThat(state.permissions.canAccept).isFalse()
|
||||
assertThat(state.permissions.canDecline).isFalse()
|
||||
assertThat(state.permissions.canBan).isFalse()
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.knockRequests).isInstanceOf(AsyncData.Loading::class.java)
|
||||
assertThat(state.permissions.canAccept).isTrue()
|
||||
assertThat(state.permissions.canDecline).isTrue()
|
||||
assertThat(state.permissions.canBan).isTrue()
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.knockRequests).isInstanceOf(AsyncData.Success::class.java)
|
||||
assertThat(state.knockRequests.dataOrNull()).isEmpty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - accept success scenario`() = runTest {
|
||||
val acceptLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
|
||||
val knockRequest = FakeKnockRequest(acceptLambda = acceptLambda)
|
||||
val knockRequests = flowOf(listOf(knockRequest))
|
||||
val presenter = createKnockRequestsListPresenter(
|
||||
knockRequestsFlow = knockRequests
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(2)
|
||||
awaitItem().also { state ->
|
||||
val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!!
|
||||
state.eventSink(KnockRequestsListEvents.Accept(knockRequestPresentable))
|
||||
}
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!!
|
||||
assertThat(state.currentAction).isEqualTo(KnockRequestsAction.Accept(knockRequestPresentable))
|
||||
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Success::class.java)
|
||||
state.eventSink(KnockRequestsListEvents.ResetCurrentAction)
|
||||
}
|
||||
skipItems(2)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||
assertThat(state.currentAction).isEqualTo(KnockRequestsAction.None)
|
||||
assertThat(state.knockRequests.dataOrNull().orEmpty()).isEmpty()
|
||||
}
|
||||
assert(acceptLambda).isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - accept failure scenario`() = runTest {
|
||||
val acceptLambda = lambdaRecorder<Result<Unit>> { Result.failure(Exception()) }
|
||||
val knockRequest = FakeKnockRequest(acceptLambda = acceptLambda)
|
||||
val knockRequests = flowOf(listOf(knockRequest))
|
||||
val presenter = createKnockRequestsListPresenter(
|
||||
knockRequestsFlow = knockRequests
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(2)
|
||||
awaitItem().also { state ->
|
||||
val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!!
|
||||
state.eventSink(KnockRequestsListEvents.Accept(knockRequestPresentable))
|
||||
}
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!!
|
||||
assertThat(state.currentAction).isEqualTo(KnockRequestsAction.Accept(knockRequestPresentable))
|
||||
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Failure::class.java)
|
||||
state.eventSink(KnockRequestsListEvents.RetryCurrentAction)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Failure::class.java)
|
||||
state.eventSink(KnockRequestsListEvents.ResetCurrentAction)
|
||||
}
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||
assertThat(state.currentAction).isEqualTo(KnockRequestsAction.None)
|
||||
assertThat(state.knockRequests.dataOrNull()).hasSize(1)
|
||||
}
|
||||
assert(acceptLambda).isCalledExactly(2)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - decline success scenario`() = runTest {
|
||||
val declineLambda = lambdaRecorder<String?, Result<Unit>> { Result.success(Unit) }
|
||||
val knockRequest = FakeKnockRequest(declineLambda = declineLambda)
|
||||
val knockRequests = flowOf(listOf(knockRequest))
|
||||
val presenter = createKnockRequestsListPresenter(
|
||||
knockRequestsFlow = knockRequests
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(2)
|
||||
awaitItem().also { state ->
|
||||
val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!!
|
||||
state.eventSink(KnockRequestsListEvents.Decline(knockRequestPresentable))
|
||||
}
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!!
|
||||
assertThat(state.currentAction).isEqualTo(KnockRequestsAction.Decline(knockRequestPresentable))
|
||||
assertThat(state.asyncAction).isInstanceOf(AsyncAction.ConfirmingNoParams::class.java)
|
||||
state.eventSink(KnockRequestsListEvents.ConfirmCurrentAction)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Success::class.java)
|
||||
state.eventSink(KnockRequestsListEvents.ResetCurrentAction)
|
||||
}
|
||||
skipItems(2)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||
assertThat(state.currentAction).isEqualTo(KnockRequestsAction.None)
|
||||
assertThat(state.knockRequests.dataOrNull().orEmpty()).isEmpty()
|
||||
}
|
||||
}
|
||||
assert(declineLambda).isCalledOnce()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - decline and ban success scenario`() = runTest {
|
||||
val declineAndBanLambda = lambdaRecorder<String?, Result<Unit>> { Result.success(Unit) }
|
||||
val knockRequest = FakeKnockRequest(declineAndBanLambda = declineAndBanLambda)
|
||||
val knockRequests = flowOf(listOf(knockRequest))
|
||||
val presenter = createKnockRequestsListPresenter(
|
||||
knockRequestsFlow = knockRequests
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(2)
|
||||
awaitItem().also { state ->
|
||||
val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!!
|
||||
state.eventSink(KnockRequestsListEvents.DeclineAndBan(knockRequestPresentable))
|
||||
}
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
val knockRequestPresentable = state.knockRequests.dataOrNull()?.first()!!
|
||||
assertThat(state.currentAction).isEqualTo(KnockRequestsAction.DeclineAndBan(knockRequestPresentable))
|
||||
assertThat(state.asyncAction).isInstanceOf(AsyncAction.ConfirmingNoParams::class.java)
|
||||
state.eventSink(KnockRequestsListEvents.ConfirmCurrentAction)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Success::class.java)
|
||||
state.eventSink(KnockRequestsListEvents.ResetCurrentAction)
|
||||
}
|
||||
skipItems(2)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||
assertThat(state.currentAction).isEqualTo(KnockRequestsAction.None)
|
||||
assertThat(state.knockRequests.dataOrNull().orEmpty()).isEmpty()
|
||||
}
|
||||
}
|
||||
assert(declineAndBanLambda).isCalledOnce()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - accept all success scenario`() = runTest {
|
||||
val acceptLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
|
||||
val knockRequests = flowOf(
|
||||
listOf(
|
||||
FakeKnockRequest(eventId = AN_EVENT_ID, acceptLambda = acceptLambda),
|
||||
FakeKnockRequest(eventId = AN_EVENT_ID_2, acceptLambda = acceptLambda),
|
||||
)
|
||||
)
|
||||
val presenter = createKnockRequestsListPresenter(
|
||||
knockRequestsFlow = knockRequests
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(2)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.canAcceptAll).isTrue()
|
||||
state.eventSink(KnockRequestsListEvents.AcceptAll)
|
||||
}
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.currentAction).isEqualTo(KnockRequestsAction.AcceptAll)
|
||||
assertThat(state.asyncAction).isInstanceOf(AsyncAction.ConfirmingNoParams::class.java)
|
||||
state.eventSink(KnockRequestsListEvents.ConfirmCurrentAction)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Success::class.java)
|
||||
state.eventSink(KnockRequestsListEvents.ResetCurrentAction)
|
||||
}
|
||||
skipItems(2)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||
assertThat(state.currentAction).isEqualTo(KnockRequestsAction.None)
|
||||
assertThat(state.knockRequests.dataOrNull().orEmpty()).isEmpty()
|
||||
}
|
||||
}
|
||||
assert(acceptLambda).isCalledExactly(2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - accept all partial success scenario`() = runTest {
|
||||
val acceptSuccessLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
|
||||
val acceptFailureLambda = lambdaRecorder<Result<Unit>> { Result.failure(Exception()) }
|
||||
val knockRequests = flowOf(
|
||||
listOf(
|
||||
FakeKnockRequest(eventId = AN_EVENT_ID, acceptLambda = acceptSuccessLambda),
|
||||
FakeKnockRequest(eventId = AN_EVENT_ID_2, acceptLambda = acceptFailureLambda),
|
||||
)
|
||||
)
|
||||
val presenter = createKnockRequestsListPresenter(
|
||||
knockRequestsFlow = knockRequests
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(2)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.canAcceptAll).isTrue()
|
||||
state.eventSink(KnockRequestsListEvents.AcceptAll)
|
||||
}
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.currentAction).isEqualTo(KnockRequestsAction.AcceptAll)
|
||||
assertThat(state.asyncAction).isInstanceOf(AsyncAction.ConfirmingNoParams::class.java)
|
||||
state.eventSink(KnockRequestsListEvents.ConfirmCurrentAction)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Loading::class.java)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Failure::class.java)
|
||||
state.eventSink(KnockRequestsListEvents.ResetCurrentAction)
|
||||
}
|
||||
skipItems(2)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.asyncAction).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||
assertThat(state.currentAction).isEqualTo(KnockRequestsAction.None)
|
||||
assertThat(state.knockRequests.dataOrNull()).hasSize(1)
|
||||
}
|
||||
}
|
||||
assert(acceptFailureLambda).isCalledOnce()
|
||||
assert(acceptSuccessLambda).isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun TestScope.createKnockRequestsListPresenter(
|
||||
canAccept: Boolean = true,
|
||||
canDecline: Boolean = true,
|
||||
canBan: Boolean = true,
|
||||
knockRequestsFlow: Flow<List<KnockRequest>> = flowOf(emptyList())
|
||||
): KnockRequestsListPresenter {
|
||||
val knockRequestsService = KnockRequestsService(
|
||||
knockRequestsFlow = knockRequestsFlow,
|
||||
coroutineScope = backgroundScope,
|
||||
isKnockFeatureEnabledFlow = flowOf(true),
|
||||
permissionsFlow = flowOf(KnockRequestPermissions(canAccept, canDecline, canBan)),
|
||||
)
|
||||
return KnockRequestsListPresenter(knockRequestsService = knockRequestsService)
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.impl.list
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.features.knockrequests.impl.R
|
||||
import io.element.android.features.knockrequests.impl.data.aKnockRequestPresentable
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.clickOn
|
||||
import io.element.android.tests.testutils.ensureCalledOnce
|
||||
import io.element.android.tests.testutils.pressBack
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class KnockRequestsListViewTest {
|
||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
@Test
|
||||
fun `clicking on back invoke the expected callback`() {
|
||||
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>(expectEvents = false)
|
||||
ensureCalledOnce {
|
||||
rule.setKnockRequestsListView(
|
||||
aKnockRequestsListState(
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
onBackClick = it
|
||||
)
|
||||
rule.pressBack()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on accept emit the expected event`() {
|
||||
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
|
||||
val knockRequest = aKnockRequestPresentable()
|
||||
rule.setKnockRequestsListView(
|
||||
aKnockRequestsListState(
|
||||
knockRequests = AsyncData.Success(persistentListOf(knockRequest)),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_accept)
|
||||
eventsRecorder.assertSingle(KnockRequestsListEvents.Accept(knockRequest))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on decline emit the expected event`() {
|
||||
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
|
||||
val knockRequest = aKnockRequestPresentable()
|
||||
rule.setKnockRequestsListView(
|
||||
aKnockRequestsListState(
|
||||
knockRequests = AsyncData.Success(persistentListOf(knockRequest)),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_decline)
|
||||
eventsRecorder.assertSingle(KnockRequestsListEvents.Decline(knockRequest))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on decline and ban emit the expected event`() {
|
||||
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
|
||||
val knockRequest = aKnockRequestPresentable()
|
||||
rule.setKnockRequestsListView(
|
||||
aKnockRequestsListState(
|
||||
knockRequests = AsyncData.Success(persistentListOf(knockRequest)),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
rule.clickOn(R.string.screen_knock_requests_list_decline_and_ban_action_title)
|
||||
eventsRecorder.assertSingle(KnockRequestsListEvents.DeclineAndBan(knockRequest))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on accept all emit the expected event`() {
|
||||
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
|
||||
val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable())
|
||||
rule.setKnockRequestsListView(
|
||||
aKnockRequestsListState(
|
||||
knockRequests = AsyncData.Success(knockRequests),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
rule.clickOn(R.string.screen_knock_requests_list_accept_all_button_title)
|
||||
eventsRecorder.assertSingle(KnockRequestsListEvents.AcceptAll)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `retry on async view retry emit the expected event`() {
|
||||
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
|
||||
val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable())
|
||||
rule.setKnockRequestsListView(
|
||||
aKnockRequestsListState(
|
||||
knockRequests = AsyncData.Success(knockRequests),
|
||||
asyncAction = AsyncAction.Failure(RuntimeException("Failed to accept all")),
|
||||
currentAction = KnockRequestsAction.AcceptAll,
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_retry)
|
||||
eventsRecorder.assertSingle(KnockRequestsListEvents.RetryCurrentAction)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `canceling async view emit the expected event`() {
|
||||
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
|
||||
val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable())
|
||||
rule.setKnockRequestsListView(
|
||||
aKnockRequestsListState(
|
||||
knockRequests = AsyncData.Success(knockRequests),
|
||||
asyncAction = AsyncAction.Failure(RuntimeException("Failed to accept all")),
|
||||
currentAction = KnockRequestsAction.AcceptAll,
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_cancel)
|
||||
eventsRecorder.assertSingle(KnockRequestsListEvents.ResetCurrentAction)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `confirming async view emit the expected event`() {
|
||||
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
|
||||
val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable())
|
||||
rule.setKnockRequestsListView(
|
||||
aKnockRequestsListState(
|
||||
knockRequests = AsyncData.Success(knockRequests),
|
||||
asyncAction = AsyncAction.ConfirmingNoParams,
|
||||
currentAction = KnockRequestsAction.AcceptAll,
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
rule.clickOn(R.string.screen_knock_requests_list_accept_all_alert_confirm_button_title)
|
||||
eventsRecorder.assertSingle(KnockRequestsListEvents.ConfirmCurrentAction)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setKnockRequestsListView(
|
||||
state: KnockRequestsListState,
|
||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||
) {
|
||||
setContent {
|
||||
KnockRequestsListView(
|
||||
state = state,
|
||||
onBackClick = onBackClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
22
features/knockrequests/test/build.gradle.kts
Normal file
22
features/knockrequests/test/build.gradle.kts
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.knockrequests.test"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.features.knockrequests.api)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.tests.testutils)
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2025 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.knockrequests.test
|
||||
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakeKnockRequestsListEntryPoint : KnockRequestsListEntryPoint {
|
||||
override fun createNode(
|
||||
parentNode: Node,
|
||||
buildContext: BuildContext,
|
||||
): Node = lambdaError()
|
||||
}
|
||||
Reference in New Issue
Block a user