From 84299fc37a9b3ef3bc01f2f6c20f8950e6641f8c Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 5 Dec 2024 12:11:24 +0100 Subject: [PATCH 1/9] knock requests : start implementing banner ui --- .../knockrequests/impl/KnockRequest.kt | 14 ++ .../impl/banner/KnockRequestsBannerState.kt | 60 ++++++ .../KnockRequestsBannerStateProvider.kt | 55 +++++ .../impl/banner/KnockRequestsBannerView.kt | 204 ++++++++++++++++++ .../list/KnockRequestsListStateProvider.kt | 15 +- .../components/avatar/AvatarSize.kt | 1 + 6 files changed, 335 insertions(+), 14 deletions(-) create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt index d9df9a5bc2..1014d13e58 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/KnockRequest.kt @@ -29,3 +29,17 @@ fun KnockRequest.getAvatarData(size: AvatarSize) = AvatarData( fun KnockRequest.getBestName(): String { return displayName?.takeIf { it.isNotEmpty() } ?: userId.value } + +fun aKnockRequest( + 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", +) = KnockRequest( + userId = userId, + displayName = displayName, + avatarUrl = avatarUrl, + reason = reason, + formattedDate = formattedDate, +) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt new file mode 100644 index 0000000000..3f993a1d40 --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.banner + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable +import androidx.compose.ui.res.pluralStringResource +import androidx.compose.ui.res.stringResource +import io.element.android.features.knockrequests.impl.KnockRequest +import io.element.android.features.knockrequests.impl.getBestName +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.ui.strings.CommonPlurals +import io.element.android.libraries.ui.strings.CommonStrings +import kotlinx.collections.immutable.ImmutableList + +@Immutable +sealed interface KnockRequestsBannerState { + data object Hidden : KnockRequestsBannerState + data class Visible( + val knockRequests: ImmutableList, + val acceptAction: AsyncAction, + val canAccept: Boolean, + ) : KnockRequestsBannerState { + + val subtitle = if (knockRequests.size == 1) { + knockRequests.first().userId.value + } else { + null + } + + val reason = if (knockRequests.size == 1) { + knockRequests.first().reason + } else { + null + } + + @Composable + fun formattedTitle(): String { + return when (knockRequests.size) { + 0 -> "" + 1 -> stringResource(CommonStrings.screen_room_single_knock_request_title, knockRequests.first().getBestName()) + else -> { + val firstRequest = knockRequests.first() + val otherRequestsCount = knockRequests.size - 1 + pluralStringResource( + id = CommonPlurals.screen_room_multiple_knock_requests_title, + count = otherRequestsCount, + firstRequest.getBestName(), + otherRequestsCount + ) + } + } + } + } +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt new file mode 100644 index 0000000000..330c919c6a --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE 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.KnockRequest +import io.element.android.features.knockrequests.impl.aKnockRequest +import io.element.android.libraries.architecture.AsyncAction +import kotlinx.collections.immutable.toImmutableList + +class KnockRequestsBannerStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + KnockRequestsBannerState.Hidden, + aVisibleKnockRequestsBannerState(), + aVisibleKnockRequestsBannerState( + knockRequests = listOf( + aKnockRequest(), + aKnockRequest(displayName = "Alice") + ) + ), + aVisibleKnockRequestsBannerState( + knockRequests = listOf( + aKnockRequest(), + aKnockRequest(displayName = "Alice"), + aKnockRequest(displayName = "Bob"), + aKnockRequest(displayName = "Charlie") + ) + ), + aVisibleKnockRequestsBannerState( + canAccept = false + ), + aVisibleKnockRequestsBannerState( + acceptAction = AsyncAction.Loading + ), + aVisibleKnockRequestsBannerState( + acceptAction = AsyncAction.Failure(Throwable()) + ), + ) +} + +fun aVisibleKnockRequestsBannerState( + knockRequests: List = listOf(aKnockRequest()), + acceptAction: AsyncAction = AsyncAction.Uninitialized, + canAccept: Boolean = true, +) = KnockRequestsBannerState.Visible( + knockRequests = knockRequests.toImmutableList(), + acceptAction = acceptAction, + canAccept = canAccept +) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt new file mode 100644 index 0000000000..a8fc985b7d --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt @@ -0,0 +1,204 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.banner + +import androidx.compose.foundation.background +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.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex +import io.element.android.compound.theme.ElementTheme +import io.element.android.compound.tokens.generated.CompoundIcons +import io.element.android.features.knockrequests.impl.KnockRequest +import io.element.android.features.knockrequests.impl.getAvatarData +import io.element.android.libraries.designsystem.components.avatar.Avatar +import io.element.android.libraries.designsystem.components.avatar.AvatarSize +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 + +private const val MAX_AVATAR_COUNT = 3 + +@Composable +fun KnockRequestsBannerView( + state: KnockRequestsBannerState, + onDismissClick: () -> Unit, + onViewRequestsClick: () -> Unit, + modifier: Modifier = Modifier, +) { + when (state) { + is KnockRequestsBannerState.Hidden -> Unit + is KnockRequestsBannerState.Visible -> VisibleKnockRequestsBannerView( + state = state, + onDismissClick = onDismissClick, + onViewRequestsClick = onViewRequestsClick, + modifier = modifier + ) + } +} + +@Composable +private fun VisibleKnockRequestsBannerView( + state: KnockRequestsBannerState.Visible, + onDismissClick: () -> Unit, + onViewRequestsClick: () -> Unit, + modifier: Modifier = Modifier, +) { + Surface( + modifier.fillMaxWidth(), + shape = MaterialTheme.shapes.small, + color = ElementTheme.colors.bgCanvasDefault, + shadowElevation = 24.dp + ) { + Column( + Modifier + .fillMaxWidth() + .padding(all = 16.dp) + ) { + Row { + KnockRequestAvatarView(state.knockRequests) + Spacer(modifier = Modifier.width(10.dp)) + Column(modifier = Modifier.weight(1f)) { + Text( + text = state.formattedTitle(), + style = ElementTheme.typography.fontBodyMdMedium, + color = MaterialTheme.colorScheme.primary, + textAlign = TextAlign.Start, + ) + if (state.subtitle != null) { + Text( + text = state.subtitle, + style = ElementTheme.typography.fontBodySmRegular, + color = MaterialTheme.colorScheme.secondary, + textAlign = TextAlign.Start, + ) + } + } + Icon( + modifier = Modifier.clickable(onClick = onDismissClick), + imageVector = CompoundIcons.Close(), + contentDescription = stringResource(CommonStrings.action_close) + ) + } + if (state.reason != null) { + Spacer(modifier = Modifier.height(16.dp)) + Text( + text = state.reason, + color = ElementTheme.colors.textPrimary, + style = ElementTheme.typography.fontBodyMdRegular, + ) + } + Spacer(modifier = Modifier.height(16.dp)) + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp)) { + if (state.knockRequests.size > 1) { + Button( + text = "View all", + onClick = onViewRequestsClick, + size = ButtonSize.MediumLowPadding, + modifier = Modifier.weight(1f), + ) + } else { + OutlinedButton( + text = "View", + onClick = onViewRequestsClick, + size = ButtonSize.MediumLowPadding, + modifier = Modifier.weight(1f), + ) + if (state.canAccept) { + Button( + text = "Accept", + onClick = {}, + size = ButtonSize.MediumLowPadding, + modifier = Modifier.weight(1f), + ) + } + } + } + } + } +} + +@Composable +private fun KnockRequestAvatarView( + knockRequests: ImmutableList, + modifier: Modifier = Modifier, +) { + Box(modifier) { + when (knockRequests.size) { + 0 -> Unit + 1 -> Avatar(knockRequests.first().getAvatarData(AvatarSize.KnockRequestBanner)) + else -> KnockRequestAvatarListView(knockRequests) + } + } +} + +@Composable +private fun KnockRequestAvatarListView( + knockRequests: ImmutableList, + modifier: Modifier = Modifier, +) { + val avatarSize = AvatarSize.KnockRequestBanner.dp + Row( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(-avatarSize / 2), + ) { + knockRequests + .take(MAX_AVATAR_COUNT) + .forEachIndexed { index, knockRequest -> + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .size(size = avatarSize) + .clip(CircleShape) + .background(color = ElementTheme.colors.bgCanvasDefault) + .zIndex(-index.toFloat()), + ) { + Avatar( + modifier = Modifier.padding(2.dp), + avatarData = knockRequest.getAvatarData(AvatarSize.KnockRequestBanner), + ) + } + } + } +} + +@Composable +@PreviewsDayNight +internal fun KnockRequestsBannerViewPreview(@PreviewParameter(KnockRequestsBannerStateProvider::class) state: KnockRequestsBannerState) = ElementPreview { + KnockRequestsBannerView( + state = state, + onDismissClick = {}, + onViewRequestsClick = {}, + modifier = Modifier.padding(16.dp) + ) +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt index 551f6b89b0..476bf556e1 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListStateProvider.kt @@ -9,6 +9,7 @@ package io.element.android.features.knockrequests.impl.list import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.knockrequests.impl.KnockRequest +import io.element.android.features.knockrequests.impl.aKnockRequest import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.matrix.api.core.UserId @@ -101,20 +102,6 @@ open class KnockRequestsListStateProvider : PreviewParameterProvider> = AsyncData.Success(persistentListOf()), currentAction: KnockRequestsCurrentAction = KnockRequestsCurrentAction.None, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt index c5bd468abe..3f7d087f41 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/avatar/AvatarSize.kt @@ -56,4 +56,5 @@ enum class AvatarSize(val dp: Dp) { Suggestion(32.dp), KnockRequestItem(52.dp), + KnockRequestBanner(32.dp), } From e744ff00ba5bcb74a04d3dc3f2f3d8a38cd370b3 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 6 Dec 2024 13:49:14 +0100 Subject: [PATCH 2/9] knock requests : update icon --- .../features/knockrequests/impl/list/KnockRequestsListView.kt | 2 +- .../android/features/roomdetails/impl/RoomDetailsView.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt index ca289eeef4..41b5553438 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/list/KnockRequestsListView.kt @@ -383,7 +383,7 @@ private fun KnockRequestsEmptyList( 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.Pin()), + iconStyle = BigIcon.Style.Default(CompoundIcons.AskToJoin()), ) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt index d77cd9e6ea..d73b8e2626 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt @@ -242,7 +242,7 @@ fun RoomDetailsView( private fun KnockRequestsItem(knockRequestsCount: Int?, onKnockRequestsClick: () -> Unit) { ListItem( headlineContent = { Text(stringResource(R.string.screen_room_details_requests_to_join_title)) }, - leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Notifications())), + leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.AskToJoin())), trailingContent = if (knockRequestsCount == null || knockRequestsCount == 0) { null } else { From 4b16ec63783b1b9b6c1809cb3ea429daa23f9ab3 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 6 Dec 2024 16:31:24 +0100 Subject: [PATCH 3/9] knock requests : branch banner in room --- features/knockrequests/api/build.gradle.kts | 2 +- .../api/banner/KnockRequestsBannerRenderer.kt | 16 ++++++++++ .../DefaultKnockRequestsBannerRenderer.kt | 32 +++++++++++++++++++ .../banner/KnockRequestsBannerPresenter.kt | 19 +++++++++++ features/messages/impl/build.gradle.kts | 1 + .../messages/impl/MessagesFlowNode.kt | 12 +++++++ .../features/messages/impl/MessagesNode.kt | 13 ++++++++ .../features/messages/impl/MessagesView.kt | 7 ++++ .../MessagesViewWithIdentityChangePreview.kt | 2 ++ .../messages/impl/MessagesViewTest.kt | 1 + 10 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/banner/KnockRequestsBannerRenderer.kt create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/DefaultKnockRequestsBannerRenderer.kt create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt diff --git a/features/knockrequests/api/build.gradle.kts b/features/knockrequests/api/build.gradle.kts index 90bcb6e568..c3eca7567c 100644 --- a/features/knockrequests/api/build.gradle.kts +++ b/features/knockrequests/api/build.gradle.kts @@ -6,7 +6,7 @@ */ plugins { - id("io.element.android-library") + id("io.element.android-compose-library") } android { diff --git a/features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/banner/KnockRequestsBannerRenderer.kt b/features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/banner/KnockRequestsBannerRenderer.kt new file mode 100644 index 0000000000..86483aee70 --- /dev/null +++ b/features/knockrequests/api/src/main/kotlin/io/element/android/features/knockrequests/api/banner/KnockRequestsBannerRenderer.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE 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) +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/DefaultKnockRequestsBannerRenderer.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/DefaultKnockRequestsBannerRenderer.kt new file mode 100644 index 0000000000..f77dd1fa91 --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/DefaultKnockRequestsBannerRenderer.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE 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 com.squareup.anvil.annotations.ContributesBinding +import io.element.android.features.knockrequests.api.banner.KnockRequestsBannerRenderer +import io.element.android.libraries.di.RoomScope +import javax.inject.Inject + +@ContributesBinding(RoomScope::class) +class DefaultKnockRequestsBannerRenderer @Inject constructor( + private val presenter: KnockRequestsBannerPresenter, +): KnockRequestsBannerRenderer { + + @Composable + override fun View(modifier: Modifier, onViewRequestsClick: () -> Unit) { + val state = presenter.present() + KnockRequestsBannerView( + state = state, + onDismissClick = {}, + onViewRequestsClick = onViewRequestsClick, + ) + } + +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt new file mode 100644 index 0000000000..3c6d0a4066 --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt @@ -0,0 +1,19 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.banner + +import androidx.compose.runtime.Composable +import io.element.android.libraries.architecture.Presenter +import javax.inject.Inject + +class KnockRequestsBannerPresenter @Inject constructor(): Presenter { + @Composable + override fun present(): KnockRequestsBannerState { + return KnockRequestsBannerState.Hidden + } +} diff --git a/features/messages/impl/build.gradle.kts b/features/messages/impl/build.gradle.kts index 824e8c1692..e87d90cbdf 100644 --- a/features/messages/impl/build.gradle.kts +++ b/features/messages/impl/build.gradle.kts @@ -65,6 +65,7 @@ dependencies { implementation(libs.vanniktech.blurhash) implementation(libs.telephoto.zoomableimage) implementation(libs.matrix.emojibase.bindings) + implementation(projects.features.knockrequests.api) testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt index 8cbb7b6d74..27b219fc1c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt @@ -26,6 +26,7 @@ import im.vector.app.features.analytics.plan.Interaction import io.element.android.anvilannotations.ContributesNode import io.element.android.features.call.api.CallType import io.element.android.features.call.api.ElementCallEntryPoint +import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint import io.element.android.features.location.api.Location import io.element.android.features.location.api.SendLocationEntryPoint import io.element.android.features.location.api.ShowLocationEntryPoint @@ -95,6 +96,7 @@ class MessagesFlowNode @AssistedInject constructor( private val mentionSpanTheme: MentionSpanTheme, private val pinnedEventsTimelineProvider: PinnedEventsTimelineProvider, private val timelineController: TimelineController, + private val knockRequestsListEntryPoint: KnockRequestsListEntryPoint, ) : BaseFlowNode( backstack = BackStack( initialElement = plugins.filterIsInstance().first().initialTarget.toNavTarget(), @@ -146,6 +148,9 @@ class MessagesFlowNode @AssistedInject constructor( @Parcelize data object PinnedMessagesList : NavTarget + + @Parcelize + data object KnockRequestsList : NavTarget } private val callbacks = plugins() @@ -226,6 +231,10 @@ class MessagesFlowNode @AssistedInject constructor( override fun onViewAllPinnedEvents() { backstack.push(NavTarget.PinnedMessagesList) } + + override fun onViewKnockRequests() { + backstack.push(NavTarget.KnockRequestsList) + } } val inputs = MessagesNode.Inputs(focusedEventId = navTarget.focusedEventId) createNode(buildContext, listOf(callback, inputs)) @@ -326,6 +335,9 @@ class MessagesFlowNode @AssistedInject constructor( NavTarget.Empty -> { node(buildContext) {} } + NavTarget.KnockRequestsList -> { + knockRequestsListEntryPoint.createNode(this, buildContext) + } } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt index 7d5bad4d63..0658309e52 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt @@ -28,6 +28,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.compound.theme.ElementTheme +import io.element.android.features.knockrequests.api.banner.KnockRequestsBannerRenderer import io.element.android.features.messages.impl.actionlist.ActionListPresenter import io.element.android.features.messages.impl.actionlist.model.TimelineItemActionPostProcessor import io.element.android.features.messages.impl.attachments.Attachment @@ -71,6 +72,7 @@ class MessagesNode @AssistedInject constructor( private val timelineItemPresenterFactories: TimelineItemPresenterFactories, private val mediaPlayer: MediaPlayer, private val permalinkParser: PermalinkParser, + private val knockRequestsBannerRenderer: KnockRequestsBannerRenderer ) : Node(buildContext, plugins = plugins), MessagesNavigator { private val presenter = presenterFactory.create( navigator = this, @@ -98,6 +100,7 @@ class MessagesNode @AssistedInject constructor( fun onEditPollClick(eventId: EventId) fun onJoinCallClick(roomId: RoomId) fun onViewAllPinnedEvents() + fun onViewKnockRequests() } override fun onBuilt() { @@ -206,6 +209,10 @@ class MessagesNode @AssistedInject constructor( callbacks.forEach { it.onJoinCallClick(room.roomId) } } + private fun onViewKnockRequestsClick() { + callbacks.forEach { it.onViewKnockRequests() } + } + @Composable override fun View(modifier: Modifier) { val activity = LocalContext.current as Activity @@ -231,6 +238,12 @@ class MessagesNode @AssistedInject constructor( onCreatePollClick = this::onCreatePollClick, onJoinCallClick = this::onJoinCallClick, onViewAllPinnedMessagesClick = this::onViewAllPinnedMessagesClick, + knockRequestsBanner = { modifier -> + knockRequestsBannerRenderer.View( + modifier = modifier, + onViewRequestsClick = this::onViewKnockRequestsClick + ) + }, modifier = modifier, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index 47e4721f7f..28ebc2a7b4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -118,6 +118,7 @@ fun MessagesView( onViewAllPinnedMessagesClick: () -> Unit, modifier: Modifier = Modifier, forceJumpToBottomVisibility: Boolean = false, + knockRequestsBanner: @Composable (Modifier) -> Unit, ) { OnLifecycleEvent { _, event -> state.voiceMessageComposerState.eventSink(VoiceMessageComposerEvents.LifecycleEvent(event)) @@ -215,6 +216,7 @@ fun MessagesView( forceJumpToBottomVisibility = forceJumpToBottomVisibility, onJoinCallClick = onJoinCallClick, onViewAllPinnedMessagesClick = onViewAllPinnedMessagesClick, + knockRequestsBanner = knockRequestsBanner, ) }, snackbarHost = { @@ -284,6 +286,7 @@ private fun MessagesViewContent( forceJumpToBottomVisibility: Boolean, onSwipeToReply: (TimelineItem.Event) -> Unit, modifier: Modifier = Modifier, + knockRequestsBanner: @Composable (Modifier) -> Unit, ) { Box( modifier = modifier @@ -372,6 +375,9 @@ private fun MessagesViewContent( onViewAllClick = onViewAllPinnedMessagesClick, ) } + Box(modifier = Modifier.padding(all = 16.dp)) { + knockRequestsBanner(Modifier) + } } }, sheetContent = { subcomposing: Boolean -> @@ -539,5 +545,6 @@ internal fun MessagesViewPreview(@PreviewParameter(MessagesStateProvider::class) onJoinCallClick = {}, onViewAllPinnedMessagesClick = { }, forceJumpToBottomVisibility = true, + knockRequestsBanner = {}, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt index 34c58bdb06..a659ce3c15 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt @@ -40,5 +40,7 @@ internal fun MessagesViewWithIdentityChangePreview( onCreatePollClick = {}, onJoinCallClick = {}, onViewAllPinnedMessagesClick = {}, + knockRequestsBanner = {} + ) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt index 1d4e1a43b3..dc68f76ee6 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt @@ -533,6 +533,7 @@ private fun AndroidComposeTestRule.setMessa onCreatePollClick = onCreatePollClick, onJoinCallClick = onJoinCallClick, onViewAllPinnedMessagesClick = onViewAllPinnedMessagesClick, + knockRequestsBanner = {} ) } } From 350a9c0464c15bcd7564c1fedd94c2906b7a8dde Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 6 Dec 2024 16:31:37 +0100 Subject: [PATCH 4/9] knock requests : change banner background --- .../knockrequests/impl/banner/KnockRequestsBannerView.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt index a8fc985b7d..8cbb93e202 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt @@ -77,7 +77,7 @@ private fun VisibleKnockRequestsBannerView( Surface( modifier.fillMaxWidth(), shape = MaterialTheme.shapes.small, - color = ElementTheme.colors.bgCanvasDefault, + color = ElementTheme.colors.bgCanvasDefaultLevel1, shadowElevation = 24.dp ) { Column( @@ -180,7 +180,7 @@ private fun KnockRequestAvatarListView( modifier = Modifier .size(size = avatarSize) .clip(CircleShape) - .background(color = ElementTheme.colors.bgCanvasDefault) + .background(color = ElementTheme.colors.bgCanvasDefaultLevel1) .zIndex(-index.toFloat()), ) { Avatar( From 603deb7b7616dadf0b117e643372e89197076276 Mon Sep 17 00:00:00 2001 From: ganfra Date: Fri, 6 Dec 2024 17:14:59 +0100 Subject: [PATCH 5/9] knock requests : refine and clean banner --- .../DefaultKnockRequestsBannerRenderer.kt | 5 +- .../impl/banner/KnockRequestsBannerEvents.kt | 15 ++ .../banner/KnockRequestsBannerPresenter.kt | 27 ++- .../impl/banner/KnockRequestsBannerState.kt | 66 ++++---- .../KnockRequestsBannerStateProvider.kt | 34 ++-- .../impl/banner/KnockRequestsBannerView.kt | 160 +++++++++--------- .../features/messages/impl/MessagesNode.kt | 4 +- .../features/messages/impl/MessagesView.kt | 46 +++-- .../MessagesViewWithIdentityChangePreview.kt | 3 +- .../messages/impl/MessagesViewTest.kt | 2 +- 10 files changed, 205 insertions(+), 157 deletions(-) create mode 100644 features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerEvents.kt diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/DefaultKnockRequestsBannerRenderer.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/DefaultKnockRequestsBannerRenderer.kt index f77dd1fa91..9fce2f2173 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/DefaultKnockRequestsBannerRenderer.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/DefaultKnockRequestsBannerRenderer.kt @@ -17,16 +17,13 @@ import javax.inject.Inject @ContributesBinding(RoomScope::class) class DefaultKnockRequestsBannerRenderer @Inject constructor( private val presenter: KnockRequestsBannerPresenter, -): KnockRequestsBannerRenderer { - +) : KnockRequestsBannerRenderer { @Composable override fun View(modifier: Modifier, onViewRequestsClick: () -> Unit) { val state = presenter.present() KnockRequestsBannerView( state = state, - onDismissClick = {}, onViewRequestsClick = onViewRequestsClick, ) } - } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerEvents.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerEvents.kt new file mode 100644 index 0000000000..28938a2c26 --- /dev/null +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerEvents.kt @@ -0,0 +1,15 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.knockrequests.impl.banner + +import io.element.android.features.knockrequests.impl.KnockRequest + +sealed interface KnockRequestsBannerEvents { + data class Accept(val knockRequest: KnockRequest) : KnockRequestsBannerEvents + data object Dismiss : KnockRequestsBannerEvents +} diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt index 3c6d0a4066..513042fc37 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt @@ -8,12 +8,35 @@ package io.element.android.features.knockrequests.impl.banner import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter +import kotlinx.collections.immutable.persistentListOf import javax.inject.Inject -class KnockRequestsBannerPresenter @Inject constructor(): Presenter { +class KnockRequestsBannerPresenter @Inject constructor() : Presenter { @Composable override fun present(): KnockRequestsBannerState { - return KnockRequestsBannerState.Hidden + var shouldShowBanner by remember { mutableStateOf(false) } + + fun handleEvents(event: KnockRequestsBannerEvents) { + when (event) { + is KnockRequestsBannerEvents.Accept -> Unit + is KnockRequestsBannerEvents.Dismiss -> { + shouldShowBanner = false + } + } + } + + return KnockRequestsBannerState( + knockRequests = persistentListOf(), + acceptAction = AsyncAction.Uninitialized, + canAccept = false, + isVisible = shouldShowBanner, + eventSink = ::handleEvents, + ) } } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt index 3f993a1d40..4d73ec8748 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt @@ -8,7 +8,6 @@ package io.element.android.features.knockrequests.impl.banner import androidx.compose.runtime.Composable -import androidx.compose.runtime.Immutable import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import io.element.android.features.knockrequests.impl.KnockRequest @@ -18,42 +17,39 @@ import io.element.android.libraries.ui.strings.CommonPlurals import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList -@Immutable -sealed interface KnockRequestsBannerState { - data object Hidden : KnockRequestsBannerState - data class Visible( - val knockRequests: ImmutableList, - val acceptAction: AsyncAction, - val canAccept: Boolean, - ) : KnockRequestsBannerState { +data class KnockRequestsBannerState( + val isVisible: Boolean, + val knockRequests: ImmutableList, + val acceptAction: AsyncAction, + val canAccept: Boolean, + val eventSink: (KnockRequestsBannerEvents) -> Unit, +) { + val subtitle = if (knockRequests.size == 1) { + knockRequests.first().userId.value + } else { + null + } - val subtitle = if (knockRequests.size == 1) { - knockRequests.first().userId.value - } else { - null - } + val reason = if (knockRequests.size == 1) { + knockRequests.first().reason + } else { + null + } - val reason = if (knockRequests.size == 1) { - knockRequests.first().reason - } else { - null - } - - @Composable - fun formattedTitle(): String { - return when (knockRequests.size) { - 0 -> "" - 1 -> stringResource(CommonStrings.screen_room_single_knock_request_title, knockRequests.first().getBestName()) - else -> { - val firstRequest = knockRequests.first() - val otherRequestsCount = knockRequests.size - 1 - pluralStringResource( - id = CommonPlurals.screen_room_multiple_knock_requests_title, - count = otherRequestsCount, - firstRequest.getBestName(), - otherRequestsCount - ) - } + @Composable + fun formattedTitle(): String { + return when (knockRequests.size) { + 0 -> "" + 1 -> stringResource(CommonStrings.screen_room_single_knock_request_title, knockRequests.first().getBestName()) + else -> { + val firstRequest = knockRequests.first() + val otherRequestsCount = knockRequests.size - 1 + pluralStringResource( + id = CommonPlurals.screen_room_multiple_knock_requests_title, + count = otherRequestsCount, + firstRequest.getBestName(), + otherRequestsCount + ) } } } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt index 330c919c6a..87295d94ee 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerStateProvider.kt @@ -16,15 +16,23 @@ import kotlinx.collections.immutable.toImmutableList class KnockRequestsBannerStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - KnockRequestsBannerState.Hidden, - aVisibleKnockRequestsBannerState(), - aVisibleKnockRequestsBannerState( + aKnockRequestsBannerState(), + aKnockRequestsBannerState( + knockRequests = listOf( + aKnockRequest( + 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( aKnockRequest(), aKnockRequest(displayName = "Alice") ) ), - aVisibleKnockRequestsBannerState( + aKnockRequestsBannerState( knockRequests = listOf( aKnockRequest(), aKnockRequest(displayName = "Alice"), @@ -32,24 +40,28 @@ class KnockRequestsBannerStateProvider : PreviewParameterProvider = listOf(aKnockRequest()), acceptAction: AsyncAction = AsyncAction.Uninitialized, canAccept: Boolean = true, -) = KnockRequestsBannerState.Visible( + isVisible: Boolean = true, + eventSink: (KnockRequestsBannerEvents) -> Unit = {} +) = KnockRequestsBannerState( knockRequests = knockRequests.toImmutableList(), acceptAction = acceptAction, - canAccept = canAccept + canAccept = canAccept, + isVisible = isVisible, + eventSink = eventSink, ) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt index 8cbb93e202..024efd2985 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt @@ -7,6 +7,9 @@ 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.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -27,6 +30,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip 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 androidx.compose.ui.zIndex @@ -52,96 +56,102 @@ private const val MAX_AVATAR_COUNT = 3 @Composable fun KnockRequestsBannerView( state: KnockRequestsBannerState, - onDismissClick: () -> Unit, onViewRequestsClick: () -> Unit, modifier: Modifier = Modifier, ) { - when (state) { - is KnockRequestsBannerState.Hidden -> Unit - is KnockRequestsBannerState.Visible -> VisibleKnockRequestsBannerView( - state = state, - onDismissClick = onDismissClick, - onViewRequestsClick = onViewRequestsClick, - modifier = modifier - ) + AnimatedVisibility( + visible = state.isVisible, + enter = expandVertically(), + exit = shrinkVertically(), + modifier = modifier, + ) { + Surface( + shape = MaterialTheme.shapes.small, + color = ElementTheme.colors.bgCanvasDefaultLevel1, + shadowElevation = 24.dp, + modifier = Modifier.padding(16.dp), + ) { + KnockRequestsBannerContent( + state = state, + onViewRequestsClick = onViewRequestsClick, + ) + } } } @Composable -private fun VisibleKnockRequestsBannerView( - state: KnockRequestsBannerState.Visible, - onDismissClick: () -> Unit, +private fun KnockRequestsBannerContent( + state: KnockRequestsBannerState, onViewRequestsClick: () -> Unit, modifier: Modifier = Modifier, ) { - Surface( - modifier.fillMaxWidth(), - shape = MaterialTheme.shapes.small, - color = ElementTheme.colors.bgCanvasDefaultLevel1, - shadowElevation = 24.dp + fun onDismissClick() { + state.eventSink(KnockRequestsBannerEvents.Dismiss) + } + + Column( + modifier + .fillMaxWidth() + .padding(all = 16.dp) ) { - Column( - Modifier - .fillMaxWidth() - .padding(all = 16.dp) - ) { - Row { - KnockRequestAvatarView(state.knockRequests) - Spacer(modifier = Modifier.width(10.dp)) - Column(modifier = Modifier.weight(1f)) { + Row { + KnockRequestAvatarView(state.knockRequests) + Spacer(modifier = Modifier.width(10.dp)) + Column(modifier = Modifier.weight(1f)) { + Text( + text = state.formattedTitle(), + style = ElementTheme.typography.fontBodyMdMedium, + color = MaterialTheme.colorScheme.primary, + textAlign = TextAlign.Start, + ) + if (state.subtitle != null) { Text( - text = state.formattedTitle(), - style = ElementTheme.typography.fontBodyMdMedium, - color = MaterialTheme.colorScheme.primary, + text = state.subtitle, + style = ElementTheme.typography.fontBodySmRegular, + color = MaterialTheme.colorScheme.secondary, textAlign = TextAlign.Start, ) - if (state.subtitle != null) { - Text( - text = state.subtitle, - style = ElementTheme.typography.fontBodySmRegular, - color = MaterialTheme.colorScheme.secondary, - textAlign = TextAlign.Start, - ) - } } - Icon( - modifier = Modifier.clickable(onClick = onDismissClick), - imageVector = CompoundIcons.Close(), - contentDescription = stringResource(CommonStrings.action_close) - ) - } - if (state.reason != null) { - Spacer(modifier = Modifier.height(16.dp)) - Text( - text = state.reason, - color = ElementTheme.colors.textPrimary, - style = ElementTheme.typography.fontBodyMdRegular, - ) } + Icon( + modifier = Modifier.clickable(onClick = ::onDismissClick), + imageVector = CompoundIcons.Close(), + contentDescription = stringResource(CommonStrings.action_close) + ) + } + if (state.reason != null) { Spacer(modifier = Modifier.height(16.dp)) - Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp)) { - if (state.knockRequests.size > 1) { + 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 = "View all", + onClick = onViewRequestsClick, + size = ButtonSize.MediumLowPadding, + modifier = Modifier.weight(1f), + ) + } else { + OutlinedButton( + text = "View", + onClick = onViewRequestsClick, + size = ButtonSize.MediumLowPadding, + modifier = Modifier.weight(1f), + ) + if (state.canAccept) { Button( - text = "View all", - onClick = onViewRequestsClick, + text = "Accept", + onClick = {}, size = ButtonSize.MediumLowPadding, modifier = Modifier.weight(1f), ) - } else { - OutlinedButton( - text = "View", - onClick = onViewRequestsClick, - size = ButtonSize.MediumLowPadding, - modifier = Modifier.weight(1f), - ) - if (state.canAccept) { - Button( - text = "Accept", - onClick = {}, - size = ButtonSize.MediumLowPadding, - modifier = Modifier.weight(1f), - ) - } } } } @@ -178,11 +188,11 @@ private fun KnockRequestAvatarListView( Box( contentAlignment = Alignment.Center, modifier = Modifier - .size(size = avatarSize) - .clip(CircleShape) - .background(color = ElementTheme.colors.bgCanvasDefaultLevel1) - .zIndex(-index.toFloat()), - ) { + .size(size = avatarSize) + .clip(CircleShape) + .background(color = ElementTheme.colors.bgCanvasDefaultLevel1) + .zIndex(-index.toFloat()), + ) { Avatar( modifier = Modifier.padding(2.dp), avatarData = knockRequest.getAvatarData(AvatarSize.KnockRequestBanner), @@ -197,8 +207,6 @@ private fun KnockRequestAvatarListView( internal fun KnockRequestsBannerViewPreview(@PreviewParameter(KnockRequestsBannerStateProvider::class) state: KnockRequestsBannerState) = ElementPreview { KnockRequestsBannerView( state = state, - onDismissClick = {}, onViewRequestsClick = {}, - modifier = Modifier.padding(16.dp) ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt index 0658309e52..4ee44fbc80 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt @@ -238,9 +238,9 @@ class MessagesNode @AssistedInject constructor( onCreatePollClick = this::onCreatePollClick, onJoinCallClick = this::onJoinCallClick, onViewAllPinnedMessagesClick = this::onViewAllPinnedMessagesClick, - knockRequestsBanner = { modifier -> + knockRequestsBannerView = { knockRequestsBannerRenderer.View( - modifier = modifier, + modifier = Modifier, onViewRequestsClick = this::onViewKnockRequestsClick ) }, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index 28ebc2a7b4..c0af0088c1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -118,7 +118,7 @@ fun MessagesView( onViewAllPinnedMessagesClick: () -> Unit, modifier: Modifier = Modifier, forceJumpToBottomVisibility: Boolean = false, - knockRequestsBanner: @Composable (Modifier) -> Unit, + knockRequestsBannerView: @Composable () -> Unit, ) { OnLifecycleEvent { _, event -> state.voiceMessageComposerState.eventSink(VoiceMessageComposerEvents.LifecycleEvent(event)) @@ -196,8 +196,8 @@ fun MessagesView( MessagesViewContent( state = state, modifier = Modifier - .padding(padding) - .consumeWindowInsets(padding), + .padding(padding) + .consumeWindowInsets(padding), onContentClick = ::onContentClick, onMessageLongClick = ::onMessageLongClick, onUserDataClick = { hidingKeyboard { onUserDataClick(it) } }, @@ -216,7 +216,7 @@ fun MessagesView( forceJumpToBottomVisibility = forceJumpToBottomVisibility, onJoinCallClick = onJoinCallClick, onViewAllPinnedMessagesClick = onViewAllPinnedMessagesClick, - knockRequestsBanner = knockRequestsBanner, + knockRequestsBannerView = knockRequestsBannerView, ) }, snackbarHost = { @@ -286,13 +286,13 @@ private fun MessagesViewContent( forceJumpToBottomVisibility: Boolean, onSwipeToReply: (TimelineItem.Event) -> Unit, modifier: Modifier = Modifier, - knockRequestsBanner: @Composable (Modifier) -> Unit, + knockRequestsBannerView: @Composable () -> Unit, ) { Box( modifier = modifier - .fillMaxSize() - .navigationBarsPadding() - .imePadding(), + .fillMaxSize() + .navigationBarsPadding() + .imePadding(), ) { AttachmentsBottomSheet( state = state.composerState, @@ -375,9 +375,7 @@ private fun MessagesViewContent( onViewAllClick = onViewAllPinnedMessagesClick, ) } - Box(modifier = Modifier.padding(all = 16.dp)) { - knockRequestsBanner(Modifier) - } + knockRequestsBannerView() } }, sheetContent = { subcomposing: Boolean -> @@ -404,13 +402,13 @@ private fun MessagesViewComposerBottomSheetContents( Column(modifier = Modifier.fillMaxWidth()) { SuggestionsPickerView( modifier = Modifier - .heightIn(max = 230.dp) - // Consume all scrolling, preventing the bottom sheet from being dragged when interacting with the list of suggestions - .nestedScroll(object : NestedScrollConnection { - override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset { - return available - } - }), + .heightIn(max = 230.dp) + // Consume all scrolling, preventing the bottom sheet from being dragged when interacting with the list of suggestions + .nestedScroll(object : NestedScrollConnection { + override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset { + return available + } + }), roomId = state.roomId, roomName = state.roomName.dataOrNull(), roomAvatarData = state.roomAvatar.dataOrNull(), @@ -458,8 +456,8 @@ private fun MessagesViewTopBar( title = { val roundedCornerShape = RoundedCornerShape(8.dp) val titleModifier = Modifier - .clip(roundedCornerShape) - .clickable { onRoomDetailsClick() } + .clip(roundedCornerShape) + .clickable { onRoomDetailsClick() } if (roomName != null && roomAvatar != null) { RoomAvatarAndNameRow( roomName = roomName, @@ -514,9 +512,9 @@ private fun RoomAvatarAndNameRow( private fun CantSendMessageBanner() { Row( modifier = Modifier - .fillMaxWidth() - .background(MaterialTheme.colorScheme.secondary) - .padding(16.dp), + .fillMaxWidth() + .background(MaterialTheme.colorScheme.secondary) + .padding(16.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center ) { @@ -545,6 +543,6 @@ internal fun MessagesViewPreview(@PreviewParameter(MessagesStateProvider::class) onJoinCallClick = {}, onViewAllPinnedMessagesClick = { }, forceJumpToBottomVisibility = true, - knockRequestsBanner = {}, + knockRequestsBannerView = {}, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt index a659ce3c15..5dc55b0dc4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt @@ -40,7 +40,6 @@ internal fun MessagesViewWithIdentityChangePreview( onCreatePollClick = {}, onJoinCallClick = {}, onViewAllPinnedMessagesClick = {}, - knockRequestsBanner = {} - + knockRequestsBannerView = {} ) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt index dc68f76ee6..b15f358828 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt @@ -533,7 +533,7 @@ private fun AndroidComposeTestRule.setMessa onCreatePollClick = onCreatePollClick, onJoinCallClick = onJoinCallClick, onViewAllPinnedMessagesClick = onViewAllPinnedMessagesClick, - knockRequestsBanner = {} + knockRequestsBannerView = {} ) } } From 7957973b5f6e682ff3b62ef71f184d0ba9737b93 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 6 Dec 2024 17:07:09 +0000 Subject: [PATCH 6/9] Update screenshots --- ...krequests.impl.banner_KnockRequestsBannerView_Day_0_en.png | 3 +++ ...krequests.impl.banner_KnockRequestsBannerView_Day_1_en.png | 3 +++ ...krequests.impl.banner_KnockRequestsBannerView_Day_2_en.png | 3 +++ ...krequests.impl.banner_KnockRequestsBannerView_Day_3_en.png | 3 +++ ...krequests.impl.banner_KnockRequestsBannerView_Day_4_en.png | 3 +++ ...krequests.impl.banner_KnockRequestsBannerView_Day_5_en.png | 3 +++ ...krequests.impl.banner_KnockRequestsBannerView_Day_6_en.png | 3 +++ ...equests.impl.banner_KnockRequestsBannerView_Night_0_en.png | 3 +++ ...equests.impl.banner_KnockRequestsBannerView_Night_1_en.png | 3 +++ ...equests.impl.banner_KnockRequestsBannerView_Night_2_en.png | 3 +++ ...equests.impl.banner_KnockRequestsBannerView_Night_3_en.png | 3 +++ ...equests.impl.banner_KnockRequestsBannerView_Night_4_en.png | 3 +++ ...equests.impl.banner_KnockRequestsBannerView_Night_5_en.png | 3 +++ ...equests.impl.banner_KnockRequestsBannerView_Night_6_en.png | 3 +++ ...knockrequests.impl.list_KnockRequestsListView_Day_1_en.png | 4 ++-- ...ockrequests.impl.list_KnockRequestsListView_Night_1_en.png | 4 ++-- ...es.designsystem.components.avatar_Avatar_Avatars_81_en.png | 3 +++ ...es.designsystem.components.avatar_Avatar_Avatars_82_en.png | 3 +++ ...es.designsystem.components.avatar_Avatar_Avatars_83_en.png | 3 +++ 19 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_1_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_2_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_3_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_4_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_5_en.png create mode 100644 tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_6_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_81_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_82_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_83_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_en.png new file mode 100644 index 0000000000..3b63b61f2d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d20f97f6422fb2eaaccd13ab12b3e27e589eaafe032a65696f3c862ccae4b743 +size 29335 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_en.png new file mode 100644 index 0000000000..1708abde01 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e0f508d82473c7b1dc9da20d25808843c3e1bfcac632ac2bf33151cc7626bf35 +size 34821 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_en.png new file mode 100644 index 0000000000..d5a84e03df --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5262c97044361ab44066a8876b8417853c8ef5c1449390242c0248c06a0fc568 +size 17855 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_en.png new file mode 100644 index 0000000000..c148a2032f --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0f247767c5f965cf75f1c7556f49bd5e3c7207b2effd9cd3d19a913581556842 +size 18744 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_en.png new file mode 100644 index 0000000000..b606b20db0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78f259c410ad179c3f93b18fc7c10829a355e5a0f56b0dd4c1f4b4efc72910f0 +size 27309 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_en.png new file mode 100644 index 0000000000..3b63b61f2d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_5_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d20f97f6422fb2eaaccd13ab12b3e27e589eaafe032a65696f3c862ccae4b743 +size 29335 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_en.png new file mode 100644 index 0000000000..3b63b61f2d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Day_6_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d20f97f6422fb2eaaccd13ab12b3e27e589eaafe032a65696f3c862ccae4b743 +size 29335 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_0_en.png new file mode 100644 index 0000000000..7b685bcbcb --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a965026cd6257bd691bdf821dcb29c99276ab5aebff2060610acc287cebf4c35 +size 27357 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_1_en.png new file mode 100644 index 0000000000..6d822fab84 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_1_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:77bc7bd3f4dab9e05ce7f68ddc50d06da03d414c33ce8e361bb83ccec38ad3c8 +size 32231 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_2_en.png new file mode 100644 index 0000000000..9c7829d9fe --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_2_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cff0d41eb0ab572946867cb9ea57fcab1fb72b155273fc68b12855c100cdb0c0 +size 15974 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_3_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_3_en.png new file mode 100644 index 0000000000..34c0b0c9ff --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_3_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8fcbb3f101b864cd0a5eeade5d43a76e42573705a90604706be696a810e7096c +size 17021 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_4_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_4_en.png new file mode 100644 index 0000000000..06ad640021 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_4_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f89ebacc5f45844b3606a214a4c0df8ca150003de50bd7a04680d85ddea2ee15 +size 25224 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_5_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_5_en.png new file mode 100644 index 0000000000..7b685bcbcb --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_5_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a965026cd6257bd691bdf821dcb29c99276ab5aebff2060610acc287cebf4c35 +size 27357 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_6_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_6_en.png new file mode 100644 index 0000000000..7b685bcbcb --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.banner_KnockRequestsBannerView_Night_6_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a965026cd6257bd691bdf821dcb29c99276ab5aebff2060610acc287cebf4c35 +size 27357 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_1_en.png index 66c6102947..2548547d60 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Day_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a9595edf8183796a0a0efa838a3c5265e07f70452933e6255d79f536cfca64ac -size 26138 +oid sha256:a67bb9ba78ea1d8802ce503e7d6a16be2ff46415df0e435efd7a36f3ee711b0d +size 26453 diff --git a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_1_en.png index 055bfccd93..613fccf45c 100644 --- a/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_1_en.png +++ b/tests/uitests/src/test/snapshots/images/features.knockrequests.impl.list_KnockRequestsListView_Night_1_en.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:37e24295a404463010f6d36ab94b1ac3c2e193d9122637abbeb0a43abcb427c1 -size 25669 +oid sha256:be2ae50ce1b0e23fec7bcbbe9ca0f0381af4d0f29f1e7498be837d9d53b75542 +size 26003 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_81_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_81_en.png new file mode 100644 index 0000000000..3c5b96d217 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_81_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5abc97134f8a5f0d037367c9278d8f007543463e13c8e043241f292949681ff6 +size 17728 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_82_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_82_en.png new file mode 100644 index 0000000000..1100737467 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_82_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:972653df5c62859c175a62d7809193afd0cb68832e3567760082f1b164e3424b +size 16990 diff --git a/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_83_en.png b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_83_en.png new file mode 100644 index 0000000000..72c687d439 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.designsystem.components.avatar_Avatar_Avatars_83_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:491cb82462df122b0e8d21c5b70e8db8ac19c28aaee1289db6f4774e7d31d53f +size 19556 From e383c7f9074d251db906eb677865a84bddfffbb5 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 9 Dec 2024 11:01:42 +0100 Subject: [PATCH 7/9] knock requests : use proper banner string resources --- .../knockrequests/impl/banner/KnockRequestsBannerState.kt | 7 +++---- .../knockrequests/impl/banner/KnockRequestsBannerView.kt | 7 ++++--- .../knockrequests/impl/src/main/res/values/localazy.xml | 8 ++++++++ libraries/ui-strings/src/main/res/values/localazy.xml | 8 -------- tools/localazy/config.json | 4 +++- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt index 4d73ec8748..5ad89b239a 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt @@ -11,10 +11,9 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import io.element.android.features.knockrequests.impl.KnockRequest +import io.element.android.features.knockrequests.impl.R import io.element.android.features.knockrequests.impl.getBestName import io.element.android.libraries.architecture.AsyncAction -import io.element.android.libraries.ui.strings.CommonPlurals -import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList data class KnockRequestsBannerState( @@ -40,12 +39,12 @@ data class KnockRequestsBannerState( fun formattedTitle(): String { return when (knockRequests.size) { 0 -> "" - 1 -> stringResource(CommonStrings.screen_room_single_knock_request_title, knockRequests.first().getBestName()) + 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 = CommonPlurals.screen_room_multiple_knock_requests_title, + id = R.plurals.screen_room_multiple_knock_requests_title, count = otherRequestsCount, firstRequest.getBestName(), otherRequestsCount diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt index 024efd2985..4ef26d460e 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt @@ -37,6 +37,7 @@ import androidx.compose.ui.zIndex import io.element.android.compound.theme.ElementTheme import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.knockrequests.impl.KnockRequest +import io.element.android.features.knockrequests.impl.R import io.element.android.features.knockrequests.impl.getAvatarData import io.element.android.libraries.designsystem.components.avatar.Avatar import io.element.android.libraries.designsystem.components.avatar.AvatarSize @@ -133,21 +134,21 @@ private fun KnockRequestsBannerContent( Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(12.dp)) { if (state.knockRequests.size > 1) { Button( - text = "View all", + 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 = "View", + 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 = "Accept", + text = stringResource(R.string.screen_room_single_knock_request_accept_button_title), onClick = {}, size = ButtonSize.MediumLowPadding, modifier = Modifier.weight(1f), diff --git a/features/knockrequests/impl/src/main/res/values/localazy.xml b/features/knockrequests/impl/src/main/res/values/localazy.xml index df14d665b8..d2e9dc5d14 100644 --- a/features/knockrequests/impl/src/main/res/values/localazy.xml +++ b/features/knockrequests/impl/src/main/res/values/localazy.xml @@ -14,4 +14,12 @@ "When somebody will ask to join the room, you’ll be able to see their request here." "No pending request to join" "Requests to join" + + "%1$s +%2$d other want to join this room" + "%1$s +%2$d others want to join this room" + + "View all" + "Accept" + "%1$s wants to join this room" + "View" diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 75b0f6853a..6e8569cae1 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -322,18 +322,10 @@ Reason: %1$s." "Your message was not sent because you have not verified one or more of your devices" "Failed processing media to upload, please try again." "Could not retrieve user details" - - "%1$s +%2$d other want to join this room" - "%1$s +%2$d others want to join this room" - - "View all" "%1$s of %2$s" "%1$s Pinned messages" "Loading message…" "View All" - "Accept" - "%1$s wants to join this room" - "View" "Chat" "Request to join sent" "Share location" diff --git a/tools/localazy/config.json b/tools/localazy/config.json index fe95d1930e..a365379344 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -291,7 +291,9 @@ { "name" : ":features:knockrequests:impl", "includeRegex" : [ - "screen\\.knock_requests_list\\..*" + "screen\\.knock_requests_list\\..*", + "screen\\.room\\.single_knock_request.*", + "screen\\.room\\.multiple_knock_requests.*" ] } ] From cc9365a2c4c3abd796e2ed44b31a292809942691 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 9 Dec 2024 11:18:04 +0100 Subject: [PATCH 8/9] misc : introduce List.firstIfSingle extension --- .../impl/banner/KnockRequestsBannerState.kt | 14 +++----------- .../android/libraries/core/extensions/List.kt | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 11 deletions(-) create mode 100644 libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/List.kt diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt index 5ad89b239a..e65cd2fb26 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerState.kt @@ -14,6 +14,7 @@ import io.element.android.features.knockrequests.impl.KnockRequest import io.element.android.features.knockrequests.impl.R import io.element.android.features.knockrequests.impl.getBestName import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.core.extensions.firstIfSingle import kotlinx.collections.immutable.ImmutableList data class KnockRequestsBannerState( @@ -23,17 +24,8 @@ data class KnockRequestsBannerState( val canAccept: Boolean, val eventSink: (KnockRequestsBannerEvents) -> Unit, ) { - val subtitle = if (knockRequests.size == 1) { - knockRequests.first().userId.value - } else { - null - } - - val reason = if (knockRequests.size == 1) { - knockRequests.first().reason - } else { - null - } + val subtitle = knockRequests.firstIfSingle()?.userId?.value + val reason = knockRequests.firstIfSingle()?.reason @Composable fun formattedTitle(): String { diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/List.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/List.kt new file mode 100644 index 0000000000..0dee04408a --- /dev/null +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/extensions/List.kt @@ -0,0 +1,15 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.core.extensions + +/** + * Returns the first element if the list contains exactly one element, otherwise returns null. + */ +inline fun List.firstIfSingle(): T? { + return if (size == 1) first() else null +} From 364a374292f587780d7ce23c1e6393f58ba8a0c1 Mon Sep 17 00:00:00 2001 From: ganfra Date: Mon, 9 Dec 2024 11:22:53 +0100 Subject: [PATCH 9/9] knock request : emit accept single request from banner --- .../knockrequests/impl/banner/KnockRequestsBannerEvents.kt | 4 +--- .../impl/banner/KnockRequestsBannerPresenter.kt | 2 +- .../knockrequests/impl/banner/KnockRequestsBannerView.kt | 6 +++++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerEvents.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerEvents.kt index 28938a2c26..14239d93ef 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerEvents.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerEvents.kt @@ -7,9 +7,7 @@ package io.element.android.features.knockrequests.impl.banner -import io.element.android.features.knockrequests.impl.KnockRequest - sealed interface KnockRequestsBannerEvents { - data class Accept(val knockRequest: KnockRequest) : KnockRequestsBannerEvents + data object AcceptSingleRequest : KnockRequestsBannerEvents data object Dismiss : KnockRequestsBannerEvents } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt index 513042fc37..a61236bbb0 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerPresenter.kt @@ -24,7 +24,7 @@ class KnockRequestsBannerPresenter @Inject constructor() : Presenter Unit + is KnockRequestsBannerEvents.AcceptSingleRequest -> Unit is KnockRequestsBannerEvents.Dismiss -> { shouldShowBanner = false } diff --git a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt index 4ef26d460e..402b8ba182 100644 --- a/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt +++ b/features/knockrequests/impl/src/main/kotlin/io/element/android/features/knockrequests/impl/banner/KnockRequestsBannerView.kt @@ -90,6 +90,10 @@ private fun KnockRequestsBannerContent( state.eventSink(KnockRequestsBannerEvents.Dismiss) } + fun onAcceptClick() { + state.eventSink(KnockRequestsBannerEvents.AcceptSingleRequest) + } + Column( modifier .fillMaxWidth() @@ -149,7 +153,7 @@ private fun KnockRequestsBannerContent( if (state.canAccept) { Button( text = stringResource(R.string.screen_room_single_knock_request_accept_button_title), - onClick = {}, + onClick = ::onAcceptClick, size = ButtonSize.MediumLowPadding, modifier = Modifier.weight(1f), )