knock requests : refine and clean banner

This commit is contained in:
ganfra 2024-12-06 17:14:59 +01:00
parent 85669e6d10
commit ef3c056952
10 changed files with 205 additions and 157 deletions

View file

@ -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,
)
}
}

View file

@ -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
}

View file

@ -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<KnockRequestsBannerState> {
class KnockRequestsBannerPresenter @Inject constructor() : Presenter<KnockRequestsBannerState> {
@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,
)
}
}

View file

@ -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,15 +17,13 @@ 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(
data class KnockRequestsBannerState(
val isVisible: Boolean,
val knockRequests: ImmutableList<KnockRequest>,
val acceptAction: AsyncAction<Unit>,
val canAccept: Boolean,
) : KnockRequestsBannerState {
val eventSink: (KnockRequestsBannerEvents) -> Unit,
) {
val subtitle = if (knockRequests.size == 1) {
knockRequests.first().userId.value
} else {
@ -56,5 +53,4 @@ sealed interface KnockRequestsBannerState {
}
}
}
}
}

View file

@ -16,15 +16,23 @@ import kotlinx.collections.immutable.toImmutableList
class KnockRequestsBannerStateProvider : PreviewParameterProvider<KnockRequestsBannerState> {
override val values: Sequence<KnockRequestsBannerState>
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<KnockRequestsB
aKnockRequest(displayName = "Charlie")
)
),
aVisibleKnockRequestsBannerState(
aKnockRequestsBannerState(
canAccept = false
),
aVisibleKnockRequestsBannerState(
aKnockRequestsBannerState(
acceptAction = AsyncAction.Loading
),
aVisibleKnockRequestsBannerState(
acceptAction = AsyncAction.Failure(Throwable())
aKnockRequestsBannerState(
acceptAction = AsyncAction.Failure(Throwable("Failed to accept knock"))
),
)
}
fun aVisibleKnockRequestsBannerState(
fun aKnockRequestsBannerState(
knockRequests: List<KnockRequest> = listOf(aKnockRequest()),
acceptAction: AsyncAction<Unit> = 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,
)

View file

@ -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,36 +56,41 @@ 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(
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,
onDismissClick = onDismissClick,
onViewRequestsClick = onViewRequestsClick,
modifier = modifier
)
}
}
}
@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
modifier
.fillMaxWidth()
.padding(all = 16.dp)
) {
@ -105,7 +114,7 @@ private fun VisibleKnockRequestsBannerView(
}
}
Icon(
modifier = Modifier.clickable(onClick = onDismissClick),
modifier = Modifier.clickable(onClick = ::onDismissClick),
imageVector = CompoundIcons.Close(),
contentDescription = stringResource(CommonStrings.action_close)
)
@ -116,6 +125,8 @@ private fun VisibleKnockRequestsBannerView(
text = state.reason,
color = ElementTheme.colors.textPrimary,
style = ElementTheme.typography.fontBodyMdRegular,
maxLines = 2,
overflow = TextOverflow.Ellipsis,
)
}
Spacer(modifier = Modifier.height(16.dp))
@ -145,7 +156,6 @@ private fun VisibleKnockRequestsBannerView(
}
}
}
}
}
@Composable
@ -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)
)
}

View file

@ -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
)
},

View file

@ -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))
@ -216,7 +216,7 @@ fun MessagesView(
forceJumpToBottomVisibility = forceJumpToBottomVisibility,
onJoinCallClick = onJoinCallClick,
onViewAllPinnedMessagesClick = onViewAllPinnedMessagesClick,
knockRequestsBanner = knockRequestsBanner,
knockRequestsBannerView = knockRequestsBannerView,
)
},
snackbarHost = {
@ -286,7 +286,7 @@ private fun MessagesViewContent(
forceJumpToBottomVisibility: Boolean,
onSwipeToReply: (TimelineItem.Event) -> Unit,
modifier: Modifier = Modifier,
knockRequestsBanner: @Composable (Modifier) -> Unit,
knockRequestsBannerView: @Composable () -> Unit,
) {
Box(
modifier = modifier
@ -375,9 +375,7 @@ private fun MessagesViewContent(
onViewAllClick = onViewAllPinnedMessagesClick,
)
}
Box(modifier = Modifier.padding(all = 16.dp)) {
knockRequestsBanner(Modifier)
}
knockRequestsBannerView()
}
},
sheetContent = { subcomposing: Boolean ->
@ -545,6 +543,6 @@ internal fun MessagesViewPreview(@PreviewParameter(MessagesStateProvider::class)
onJoinCallClick = {},
onViewAllPinnedMessagesClick = { },
forceJumpToBottomVisibility = true,
knockRequestsBanner = {},
knockRequestsBannerView = {},
)
}

View file

@ -40,7 +40,6 @@ internal fun MessagesViewWithIdentityChangePreview(
onCreatePollClick = {},
onJoinCallClick = {},
onViewAllPinnedMessagesClick = {},
knockRequestsBanner = {}
knockRequestsBannerView = {}
)
}

View file

@ -533,7 +533,7 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setMessa
onCreatePollClick = onCreatePollClick,
onJoinCallClick = onJoinCallClick,
onViewAllPinnedMessagesClick = onViewAllPinnedMessagesClick,
knockRequestsBanner = {}
knockRequestsBannerView = {}
)
}
}