Disappearing messages tweaks

Re-using our new radio buttons in disappearing messages
Tweaking UI to match designs
This commit is contained in:
ThomasSession 2024-07-15 15:07:08 +10:00
parent e193105902
commit a1fe1f1406
7 changed files with 163 additions and 87 deletions

View File

@ -3,7 +3,9 @@ package org.thoughtcrime.securesms.conversation.disappearingmessages.ui
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
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.rememberScrollState
import androidx.compose.foundation.verticalScroll
@ -43,13 +45,17 @@ fun DisappearingMessages(
Box(modifier = Modifier.weight(1f)) {
Column(
modifier = Modifier
.padding(bottom = 20.dp)
.padding(vertical = LocalDimensions.current.spacing)
.verticalScroll(scrollState)
.fadingEdges(scrollState),
verticalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallSpacing)
) {
state.cards.forEach {
OptionsCard(it, callbacks)
state.cards.forEachIndexed { index, option ->
OptionsCard(option, callbacks)
// add spacing if not the last item
if(index != state.cards.lastIndex){
Spacer(modifier = Modifier.height(LocalDimensions.current.spacing))
}
}
if (state.showGroupFooter) Text(
@ -58,7 +64,9 @@ fun DisappearingMessages(
fontWeight = FontWeight(400),
color = LocalColors.current.textSecondary,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
modifier = Modifier
.fillMaxWidth()
.padding(top = LocalDimensions.current.xsSpacing)
)
}
}
@ -68,7 +76,7 @@ fun DisappearingMessages(
modifier = Modifier
.contentDescription(R.string.AccessibilityId_set_button)
.align(Alignment.CenterHorizontally)
.padding(bottom = 20.dp),
.padding(bottom = LocalDimensions.current.spacing),
onClick = callbacks::onSetClick
)
}

View File

@ -5,7 +5,7 @@ import network.loki.messenger.libsession_util.util.ExpiryMode
import org.thoughtcrime.securesms.ui.GetString
import org.thoughtcrime.securesms.ui.RadioOption
typealias ExpiryOptionsCard = OptionsCard<ExpiryMode>
typealias ExpiryOptionsCard = OptionsCardData<ExpiryMode>
data class UiState(
val cards: List<ExpiryOptionsCard> = emptyList(),
@ -23,7 +23,7 @@ data class UiState(
)
}
data class OptionsCard<T>(
data class OptionsCardData<T>(
val title: GetString,
val options: List<RadioOption<T>>
) {

View File

@ -100,7 +100,7 @@ private fun NotificationRadioButton(
RadioButton(
onClick = onClick,
modifier = modifier,
checked = checked,
selected = checked,
contentPadding = PaddingValues(horizontal = LocalDimensions.current.mediumSpacing, vertical = 7.dp)
) {
Box(

View File

@ -7,12 +7,9 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
@ -21,7 +18,6 @@ import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.LazyColumn
@ -31,7 +27,6 @@ import androidx.compose.material.Card
import androidx.compose.material.Icon
import androidx.compose.material.LocalContentColor
import androidx.compose.material.MaterialTheme
import androidx.compose.material.RadioButton
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.runtime.Composable
@ -64,16 +59,15 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import network.loki.messenger.R
import org.session.libsession.utilities.recipients.Recipient
import org.session.libsession.utilities.runIf
import org.thoughtcrime.securesms.components.ProfilePictureView
import org.thoughtcrime.securesms.conversation.disappearingmessages.ui.OptionsCard
import org.thoughtcrime.securesms.conversation.disappearingmessages.ui.OptionsCardData
import org.thoughtcrime.securesms.ui.components.SmallCircularProgressIndicator
import org.thoughtcrime.securesms.ui.components.TitledRadioButton
import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import org.thoughtcrime.securesms.ui.theme.LocalType
import org.thoughtcrime.securesms.ui.theme.PreviewTheme
import org.thoughtcrime.securesms.ui.theme.divider
import org.thoughtcrime.securesms.ui.theme.radioButtonColors
import org.thoughtcrime.securesms.ui.theme.transparentButtonColors
import kotlin.math.min
import kotlin.math.roundToInt
@ -98,18 +92,23 @@ data class RadioOption<T>(
)
@Composable
fun <T> OptionsCard(card: OptionsCard<T>, callbacks: Callbacks<T>) {
fun <T> ColumnScope.OptionsCard(card: OptionsCardData<T>, callbacks: Callbacks<T>) {
Text(
card.title(),
style = LocalType.current.base
modifier = Modifier.padding(start = LocalDimensions.current.smallSpacing),
text = card.title(),
style = LocalType.current.base,
color = LocalColors.current.textSecondary
)
Spacer(modifier = Modifier.height(LocalDimensions.current.xsSpacing))
CellNoMargin {
LazyColumn(
modifier = Modifier.heightIn(max = 5000.dp)
) {
itemsIndexed(card.options) { i, it ->
if (i != 0) Divider()
TitledRadioButton(it) { callbacks.setValue(it.value) }
TitledRadioButton(option = it) { callbacks.setValue(it.value) }
}
}
}
@ -284,47 +283,6 @@ fun CellWithPaddingAndMargin(
}
}
@Composable
fun <T> TitledRadioButton(option: RadioOption<T>, onClick: () -> Unit) {
val color = if (option.enabled) LocalColors.current.text else LocalColors.current.disabled
Row(
horizontalArrangement = Arrangement.spacedBy(LocalDimensions.current.smallSpacing),
modifier = Modifier
.runIf(option.enabled) { clickable { if (!option.selected) onClick() } }
.heightIn(min = 60.dp)
.padding(horizontal = LocalDimensions.current.spacing)
.contentDescription(option.contentDescription)
) {
Column(modifier = Modifier
.weight(1f)
.align(Alignment.CenterVertically)) {
Column {
Text(
text = option.title(),
style = LocalType.current.large,
color = color
)
option.subtitle?.let {
Text(
text = it(),
style = LocalType.current.extraSmall,
color = color
)
}
}
}
RadioButton(
selected = option.selected,
onClick = null,
modifier = Modifier
.height(26.dp)
.align(Alignment.CenterVertically),
enabled = option.enabled,
colors = LocalColors.current.radioButtonColors()
)
}
}
@Composable
fun Modifier.contentDescription(text: GetString?): Modifier {
return text?.let {

View File

@ -1,39 +1,58 @@
package org.thoughtcrime.securesms.ui.components
import android.content.ContentProvider
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.LocalContentColor
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import network.loki.messenger.libsession_util.util.ExpiryMode
import org.thoughtcrime.securesms.conversation.disappearingmessages.ExpiryType
import org.thoughtcrime.securesms.ui.GetString
import org.thoughtcrime.securesms.ui.RadioOption
import org.thoughtcrime.securesms.ui.contentDescription
import org.thoughtcrime.securesms.ui.theme.LocalColors
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import org.thoughtcrime.securesms.ui.theme.LocalType
import org.thoughtcrime.securesms.ui.theme.PreviewTheme
import org.thoughtcrime.securesms.ui.theme.sessionTypography
import org.thoughtcrime.securesms.ui.theme.textEnabled
import org.thoughtcrime.securesms.ui.theme.transparentButtonColors
import kotlin.time.Duration.Companion.days
@Composable
fun RadioButton(
onClick: () -> Unit = {},
modifier: Modifier = Modifier,
checked: Boolean = false,
selected: Boolean = false,
enabled: Boolean = true,
contentPadding: PaddingValues = PaddingValues(),
content: @Composable RowScope.() -> Unit = {}
) {
@ -41,20 +60,22 @@ fun RadioButton(
modifier = modifier
.fillMaxWidth()
.selectable(
selected = checked,
selected = selected,
enabled = true,
role = Role.RadioButton,
onClick = onClick
),
enabled = enabled,
colors = transparentButtonColors(),
onClick = onClick,
shape = RectangleShape,
contentPadding = contentPadding
) {
content()
Spacer(modifier = Modifier.width(20.dp))
RadioButtonIndicator(
checked = checked,
selected = selected && enabled, // disabled radio shouldn't be selected
modifier = Modifier
.size(22.dp)
.align(Alignment.CenterVertically)
@ -64,12 +85,12 @@ fun RadioButton(
@Composable
private fun RadioButtonIndicator(
checked: Boolean,
selected: Boolean,
modifier: Modifier
) {
Box(modifier = modifier) {
AnimatedVisibility(
checked,
selected,
modifier = Modifier
.padding(2.5.dp)
.clip(CircleShape),
@ -90,9 +111,93 @@ private fun RadioButtonIndicator(
.aspectRatio(1f)
.border(
width = LocalDimensions.current.borderStroke,
color = LocalColors.current.text,
color = LocalContentColor.current,
shape = CircleShape
)
) {}
}
}
@Composable
fun <T> TitledRadioButton(
modifier: Modifier = Modifier,
option: RadioOption<T>,
onClick: () -> Unit
) {
RadioButton(
modifier = modifier.heightIn(min = 60.dp)
.contentDescription(option.contentDescription),
onClick = onClick,
selected = option.selected,
enabled = option.enabled,
contentPadding = PaddingValues(horizontal = LocalDimensions.current.spacing),
content = {
Column(
modifier = Modifier
.weight(1f)
.align(Alignment.CenterVertically)
) {
Column {
Text(
text = option.title(),
style = LocalType.current.large
)
option.subtitle?.let {
Text(
text = it(),
style = LocalType.current.extraSmall
)
}
}
}
}
)
}
@Preview
@Composable
fun PreviewTextRadioButton() {
PreviewTheme {
TitledRadioButton(
option = RadioOption<ExpiryMode>(
value = ExpiryType.AFTER_SEND.mode(7.days),
title = GetString(7.days),
subtitle = GetString("This is a subtitle"),
enabled = true,
selected = true
)
) {}
}
}
@Preview
@Composable
fun PreviewDisabledTextRadioButton() {
PreviewTheme {
TitledRadioButton(
option = RadioOption<ExpiryMode>(
value = ExpiryType.AFTER_SEND.mode(7.days),
title = GetString(7.days),
subtitle = GetString("This is a subtitle"),
enabled = false,
selected = true
)
) {}
}
}
@Preview
@Composable
fun PreviewDeselectedTextRadioButton() {
PreviewTheme {
TitledRadioButton(
option = RadioOption<ExpiryMode>(
value = ExpiryType.AFTER_SEND.mode(7.days),
title = GetString(7.days),
subtitle = GetString("This is a subtitle"),
enabled = true,
selected = false
)
) {}
}
}

View File

@ -46,6 +46,7 @@ val ThemeColors.divider get() = text.copy(alpha = TabRowDefaults.DividerOpacity)
fun ThemeColors.text(isError: Boolean): Color = if (isError) danger else text
fun ThemeColors.textSecondary(isError: Boolean): Color = if (isError) danger else textSecondary
fun ThemeColors.textEnabled(enabled: Boolean) = if (enabled) text else disabled
fun ThemeColors.borders(isError: Boolean): Color = if (isError) danger else borders
fun ThemeColors.toMaterialColors() = androidx.compose.material.Colors(
@ -73,7 +74,11 @@ fun ThemeColors.radioButtonColors() = RadioButtonDefaults.colors(
)
@Composable
fun transparentButtonColors() = ButtonDefaults.buttonColors(backgroundColor = Color.Transparent)
fun transparentButtonColors() = ButtonDefaults.buttonColors(
backgroundColor = Color.Transparent,
disabledBackgroundColor = Color.Transparent,
disabledContentColor = LocalColors.current.disabled
)
@Composable
fun dangerButtonColors() = ButtonDefaults.buttonColors(backgroundColor = Color.Transparent, contentColor = LocalColors.current.danger)

View File

@ -25,7 +25,7 @@ import org.session.libsession.utilities.recipients.Recipient
import org.session.libsignal.utilities.guava.Optional
import org.thoughtcrime.securesms.MainCoroutineRule
import org.thoughtcrime.securesms.conversation.disappearingmessages.ui.ExpiryRadioOption
import org.thoughtcrime.securesms.conversation.disappearingmessages.ui.OptionsCard
import org.thoughtcrime.securesms.conversation.disappearingmessages.ui.OptionsCardData
import org.thoughtcrime.securesms.conversation.disappearingmessages.ui.UiState
import org.thoughtcrime.securesms.database.GroupDatabase
import org.thoughtcrime.securesms.database.Storage
@ -87,7 +87,7 @@ class DisappearingMessagesViewModelTest {
viewModel.uiState.value
).isEqualTo(
UiState(
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_timer,
typeOption(ExpiryMode.NONE, selected = true),
timeOption(ExpiryType.AFTER_SEND, 12.hours),
@ -126,7 +126,7 @@ class DisappearingMessagesViewModelTest {
viewModel.uiState.value
).isEqualTo(
UiState(
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_timer,
typeOption(ExpiryMode.NONE, selected = false),
timeOption(ExpiryType.LEGACY, 12.hours),
@ -165,7 +165,7 @@ class DisappearingMessagesViewModelTest {
viewModel.uiState.value
).isEqualTo(
UiState(
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_timer,
typeOption(ExpiryMode.NONE, selected = true),
timeOption(ExpiryType.AFTER_SEND, 12.hours),
@ -205,7 +205,7 @@ class DisappearingMessagesViewModelTest {
viewModel.uiState.value
).isEqualTo(
UiState(
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_timer,
typeOption(ExpiryMode.NONE, enabled = false, selected = true),
timeOption(ExpiryType.AFTER_SEND, 12.hours, enabled = false),
@ -247,7 +247,7 @@ class DisappearingMessagesViewModelTest {
viewModel.uiState.value
).isEqualTo(
UiState(
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_delete_type,
typeOption(ExpiryMode.NONE, selected = true),
typeOption(12.hours, ExpiryType.AFTER_READ),
@ -286,13 +286,13 @@ class DisappearingMessagesViewModelTest {
viewModel.uiState.value
).isEqualTo(
UiState(
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_delete_type,
typeOption(ExpiryMode.NONE),
typeOption(time, ExpiryType.AFTER_READ),
typeOption(time, ExpiryType.AFTER_SEND, selected = true)
),
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_timer,
timeOption(ExpiryType.AFTER_SEND, 12.hours, selected = true),
timeOption(ExpiryType.AFTER_SEND, 1.days),
@ -332,14 +332,14 @@ class DisappearingMessagesViewModelTest {
viewModel.uiState.value
).isEqualTo(
UiState(
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_delete_type,
typeOption(ExpiryMode.NONE),
typeOption(time, ExpiryType.LEGACY, selected = true),
typeOption(12.hours, ExpiryType.AFTER_READ, enabled = false),
typeOption(1.days, ExpiryType.AFTER_SEND, enabled = false)
),
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_timer,
timeOption(ExpiryType.LEGACY, 12.hours, selected = true),
timeOption(ExpiryType.LEGACY, 1.days),
@ -379,13 +379,13 @@ class DisappearingMessagesViewModelTest {
viewModel.uiState.value
).isEqualTo(
UiState(
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_delete_type,
typeOption(ExpiryMode.NONE),
typeOption(12.hours, ExpiryType.AFTER_READ),
typeOption(time, ExpiryType.AFTER_SEND, selected = true)
),
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_timer,
timeOption(ExpiryType.AFTER_SEND, 12.hours),
timeOption(ExpiryType.AFTER_SEND, 1.days, selected = true),
@ -426,13 +426,13 @@ class DisappearingMessagesViewModelTest {
viewModel.uiState.value
).isEqualTo(
UiState(
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_delete_type,
typeOption(ExpiryMode.NONE),
typeOption(1.days, ExpiryType.AFTER_READ, selected = true),
typeOption(time, ExpiryType.AFTER_SEND)
),
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_timer,
timeOption(ExpiryType.AFTER_READ, 5.minutes),
timeOption(ExpiryType.AFTER_READ, 1.hours),
@ -479,13 +479,13 @@ class DisappearingMessagesViewModelTest {
viewModel.uiState.value
).isEqualTo(
UiState(
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_delete_type,
typeOption(ExpiryMode.NONE),
typeOption(12.hours, ExpiryType.AFTER_READ),
typeOption(1.days, ExpiryType.AFTER_SEND, selected = true)
),
OptionsCard(
OptionsCardData(
R.string.activity_disappearing_messages_timer,
timeOption(ExpiryType.AFTER_SEND, 12.hours),
timeOption(ExpiryType.AFTER_SEND, 1.days, selected = true),