Make verification screens scrollable and emoji labels multiline (#4449)
* Make self verification screens scrollable * Remove unused fields from `VerificationEmoji` * Make only the header and content scroll in `HeaderFooterPage`. * Use the right 'emoji' icon in both flows (`ReactionSolid`) --------- Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
committed by
GitHub
parent
93d27735e4
commit
d27a61a588
@@ -85,7 +85,7 @@ fun JoinRoomView(
|
||||
) {
|
||||
HeaderFooterPage(
|
||||
containerColor = Color.Transparent,
|
||||
paddingValues = PaddingValues(
|
||||
contentPadding = PaddingValues(
|
||||
horizontal = 16.dp,
|
||||
vertical = 32.dp
|
||||
),
|
||||
|
||||
@@ -51,7 +51,7 @@ fun RoomAliasResolverView(
|
||||
) {
|
||||
HeaderFooterPage(
|
||||
containerColor = Color.Transparent,
|
||||
paddingValues = PaddingValues(
|
||||
contentPadding = PaddingValues(
|
||||
horizontal = 16.dp,
|
||||
vertical = 32.dp
|
||||
),
|
||||
|
||||
@@ -13,9 +13,11 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.TopAppBarDefaults.topAppBarColors
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
@@ -35,6 +37,7 @@ import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
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.InvisibleButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
@@ -67,7 +70,8 @@ fun IncomingVerificationView(
|
||||
step is Step.Completed -> Unit
|
||||
else -> BackButton(onClick = { state.eventSink(IncomingVerificationViewEvents.GoBack) })
|
||||
}
|
||||
}
|
||||
},
|
||||
colors = topAppBarColors(containerColor = Color.Transparent),
|
||||
)
|
||||
},
|
||||
header = {
|
||||
@@ -77,7 +81,8 @@ fun IncomingVerificationView(
|
||||
IncomingVerificationBottomMenu(
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
},
|
||||
isScrollable = true,
|
||||
) {
|
||||
IncomingVerificationContent(
|
||||
step = step,
|
||||
@@ -222,7 +227,11 @@ private fun IncomingVerificationBottomMenu(
|
||||
}
|
||||
is Step.Verifying -> {
|
||||
if (step.isWaiting) {
|
||||
// Show nothing
|
||||
// Add invisible buttons to keep the same screen layout
|
||||
VerificationBottomMenu {
|
||||
InvisibleButton()
|
||||
InvisibleButton()
|
||||
}
|
||||
} else {
|
||||
VerificationBottomMenu {
|
||||
Button(
|
||||
|
||||
@@ -16,9 +16,11 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.TopAppBarDefaults.topAppBarColors
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -91,7 +93,8 @@ fun VerifySelfSessionView(
|
||||
{ BackButton(onClick = ::cancelOrResetFlow) }
|
||||
} else {
|
||||
{}
|
||||
}
|
||||
},
|
||||
colors = topAppBarColors(containerColor = Color.Transparent)
|
||||
)
|
||||
},
|
||||
header = {
|
||||
@@ -103,7 +106,8 @@ fun VerifySelfSessionView(
|
||||
onCancelClick = ::cancelOrResetFlow,
|
||||
onContinueClick = onFinish,
|
||||
)
|
||||
}
|
||||
},
|
||||
isScrollable = true,
|
||||
) {
|
||||
VerifySelfSessionContent(
|
||||
flowState = step,
|
||||
@@ -124,13 +128,13 @@ private fun VerifySelfSessionHeader(step: Step, request: VerificationRequest.Out
|
||||
}
|
||||
Step.AwaitingOtherDeviceResponse -> BigIcon.Style.Loading
|
||||
Step.Canceled -> BigIcon.Style.AlertSolid
|
||||
Step.Ready -> BigIcon.Style.Default(CompoundIcons.Reaction())
|
||||
Step.Ready -> BigIcon.Style.Default(CompoundIcons.ReactionSolid())
|
||||
Step.Completed -> BigIcon.Style.SuccessSolid
|
||||
is Step.Verifying -> {
|
||||
if (step.state is AsyncData.Loading<Unit>) {
|
||||
BigIcon.Style.Loading
|
||||
} else {
|
||||
BigIcon.Style.Default(CompoundIcons.Reaction())
|
||||
BigIcon.Style.Default(CompoundIcons.ReactionSolid())
|
||||
}
|
||||
}
|
||||
is Step.Exit -> return
|
||||
@@ -272,7 +276,11 @@ private fun VerifySelfSessionBottomMenu(
|
||||
is Step.AwaitingOtherDeviceResponse -> Unit
|
||||
is Step.Verifying -> {
|
||||
if (isVerifying) {
|
||||
// Show nothing
|
||||
// Add invisible buttons to keep the same screen layout
|
||||
VerificationBottomMenu {
|
||||
InvisibleButton()
|
||||
InvisibleButton()
|
||||
}
|
||||
} else {
|
||||
VerificationBottomMenu {
|
||||
Button(
|
||||
|
||||
@@ -23,11 +23,11 @@ internal fun aDecimalsSessionVerificationData(
|
||||
}
|
||||
|
||||
private fun aVerificationEmojiList() = listOf(
|
||||
VerificationEmoji(number = 27, emoji = "🍕", description = "Pizza"),
|
||||
VerificationEmoji(number = 54, emoji = "🚀", description = "Rocket"),
|
||||
VerificationEmoji(number = 54, emoji = "🚀", description = "Rocket"),
|
||||
VerificationEmoji(number = 42, emoji = "📕", description = "Book"),
|
||||
VerificationEmoji(number = 48, emoji = "🔨", description = "Hammer"),
|
||||
VerificationEmoji(number = 48, emoji = "🔨", description = "Hammer"),
|
||||
VerificationEmoji(number = 63, emoji = "📌", description = "Pin"),
|
||||
VerificationEmoji(number = 27),
|
||||
VerificationEmoji(number = 54),
|
||||
VerificationEmoji(number = 54),
|
||||
VerificationEmoji(number = 42),
|
||||
VerificationEmoji(number = 48),
|
||||
VerificationEmoji(number = 48),
|
||||
VerificationEmoji(number = 63),
|
||||
)
|
||||
|
||||
@@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -38,7 +39,7 @@ internal fun VerificationContentVerifying(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
modifier = modifier.fillMaxSize().padding(bottom = 20.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
when (data) {
|
||||
@@ -86,8 +87,8 @@ private fun EmojiItemView(emoji: VerificationEmoji, modifier: Modifier = Modifie
|
||||
text = stringResource(id = emojiResource.nameRes),
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
overflow = TextOverflow.Visible,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ class VerifySelfSessionPresenterTest {
|
||||
@Test
|
||||
fun `present - When verification is approved, the flow completes if there is no error`() = runTest {
|
||||
val emojis = listOf(
|
||||
VerificationEmoji(number = 30, emoji = "😀", description = "Smiley")
|
||||
VerificationEmoji(number = 30)
|
||||
)
|
||||
val service = unverifiedSessionService(
|
||||
requestSessionVerificationLambda = { },
|
||||
|
||||
@@ -10,18 +10,21 @@ package io.element.android.libraries.designsystem.atomic.pages
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.calculateEndPadding
|
||||
import androidx.compose.foundation.layout.calculateStartPadding
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.movableContentOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
@@ -31,7 +34,7 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
||||
|
||||
/**
|
||||
* @param modifier Classical modifier.
|
||||
* @param paddingValues padding values to apply to the content.
|
||||
* @param contentPadding padding values to apply to the content.
|
||||
* @param containerColor color of the container. Set to [Color.Transparent] if you provide a background in the [modifier].
|
||||
* @param isScrollable if the whole content should be scrollable.
|
||||
* @param background optional background component.
|
||||
@@ -44,7 +47,7 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
||||
@Composable
|
||||
fun HeaderFooterPage(
|
||||
modifier: Modifier = Modifier,
|
||||
paddingValues: PaddingValues = PaddingValues(20.dp),
|
||||
contentPadding: PaddingValues = PaddingValues(20.dp),
|
||||
containerColor: Color = ElementTheme.colors.bgCanvasDefault,
|
||||
isScrollable: Boolean = false,
|
||||
background: @Composable () -> Unit = {},
|
||||
@@ -53,64 +56,67 @@ fun HeaderFooterPage(
|
||||
footer: @Composable () -> Unit = {},
|
||||
content: @Composable () -> Unit = {},
|
||||
) {
|
||||
val topBar = remember { movableContentOf(topBar) }
|
||||
val header = remember { movableContentOf(header) }
|
||||
val footer = remember { movableContentOf(footer) }
|
||||
val content = remember { movableContentOf(content) }
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
topBar = topBar,
|
||||
containerColor = containerColor,
|
||||
) { padding ->
|
||||
) { insetsPadding ->
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
val contentInsetsPadding = remember(insetsPadding, layoutDirection) {
|
||||
PaddingValues(
|
||||
start = insetsPadding.calculateStartPadding(layoutDirection),
|
||||
top = insetsPadding.calculateTopPadding(),
|
||||
end = insetsPadding.calculateEndPadding(layoutDirection),
|
||||
)
|
||||
}
|
||||
val footerInsetsPadding = remember(insetsPadding, layoutDirection) {
|
||||
PaddingValues(
|
||||
start = insetsPadding.calculateStartPadding(layoutDirection),
|
||||
end = insetsPadding.calculateEndPadding(layoutDirection),
|
||||
bottom = insetsPadding.calculateBottomPadding(),
|
||||
)
|
||||
}
|
||||
Box {
|
||||
background()
|
||||
if (isScrollable) {
|
||||
// Render in a LazyColumn
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues = paddingValues)
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
.imePadding()
|
||||
) {
|
||||
// Header
|
||||
item {
|
||||
header()
|
||||
}
|
||||
// Content
|
||||
item {
|
||||
content()
|
||||
}
|
||||
// Footer
|
||||
item {
|
||||
Box(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||
footer()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Render in a Column
|
||||
|
||||
// Render in a Column
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues = contentPadding)
|
||||
.consumeWindowInsets(insetsPadding)
|
||||
.imePadding(),
|
||||
) {
|
||||
// Content
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues = paddingValues)
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
.imePadding()
|
||||
.fillMaxWidth()
|
||||
.run {
|
||||
if (isScrollable) {
|
||||
verticalScroll(rememberScrollState())
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
}
|
||||
// Apply insets here so if the content is scrollable it can get below the top app bar if needed
|
||||
.padding(contentInsetsPadding)
|
||||
.weight(1f),
|
||||
) {
|
||||
// Header
|
||||
header()
|
||||
// Content
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxWidth(),
|
||||
) {
|
||||
Box(modifier = Modifier.weight(1f)) {
|
||||
content()
|
||||
}
|
||||
// Footer
|
||||
Box(modifier = Modifier.padding(horizontal = 16.dp)) {
|
||||
footer()
|
||||
}
|
||||
}
|
||||
|
||||
// Footer
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.fillMaxWidth()
|
||||
.padding(footerInsetsPadding)
|
||||
) {
|
||||
footer()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,8 +129,7 @@ internal fun HeaderFooterPagePreview() = ElementPreview {
|
||||
HeaderFooterPage(
|
||||
content = {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxSize(),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
@@ -159,3 +164,46 @@ internal fun HeaderFooterPagePreview() = ElementPreview {
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun HeaderFooterPageScrollablePreview() = ElementPreview {
|
||||
HeaderFooterPage(
|
||||
content = {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "Content",
|
||||
style = ElementTheme.typography.fontHeadingXlBold
|
||||
)
|
||||
}
|
||||
},
|
||||
header = {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "Header",
|
||||
style = ElementTheme.typography.fontHeadingXlBold
|
||||
)
|
||||
}
|
||||
},
|
||||
footer = {
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "Footer",
|
||||
style = ElementTheme.typography.fontHeadingXlBold
|
||||
)
|
||||
}
|
||||
},
|
||||
isScrollable = true,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -25,6 +25,4 @@ sealed interface SessionVerificationData {
|
||||
// https://spec.matrix.org/unstable/client-server-api/#sas-method-emoji
|
||||
data class VerificationEmoji(
|
||||
val number: Int,
|
||||
val emoji: String,
|
||||
val description: String,
|
||||
)
|
||||
|
||||
@@ -274,8 +274,6 @@ private fun RustSessionVerificationData.map(): SessionVerificationData {
|
||||
emoji.use { sessionVerificationEmoji ->
|
||||
VerificationEmoji(
|
||||
number = sessionVerificationData.indices[index].toInt(),
|
||||
emoji = sessionVerificationEmoji.symbol(),
|
||||
description = sessionVerificationEmoji.description(),
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -68,6 +68,7 @@ class KonsistPreviewTest {
|
||||
"DefaultRoomListTopBarWithIndicatorPreview",
|
||||
"FocusedEventPreview",
|
||||
"GradientFloatingActionButtonCircleShapePreview",
|
||||
"HeaderFooterPageScrollablePreview",
|
||||
"IconsCompoundPreview",
|
||||
"IconsOtherPreview",
|
||||
"MarkdownTextComposerEditPreview",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user