diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/ui/DisappearingMessages.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/ui/DisappearingMessages.kt index cdae99fcc9..46d301cdc4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/ui/DisappearingMessages.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/ui/DisappearingMessages.kt @@ -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 ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/ui/UiState.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/ui/UiState.kt index 40f917427c..64ef353e5b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/ui/UiState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/disappearingmessages/ui/UiState.kt @@ -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 +typealias ExpiryOptionsCard = OptionsCardData data class UiState( val cards: List = emptyList(), @@ -23,7 +23,7 @@ data class UiState( ) } -data class OptionsCard( +data class OptionsCardData( val title: GetString, val options: List> ) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/messagenotifications/MessageNotifications.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/messagenotifications/MessageNotifications.kt index 3c53c469de..2dfaf40741 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/messagenotifications/MessageNotifications.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/messagenotifications/MessageNotifications.kt @@ -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( diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt index 89f3a05372..206a2a72cc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/Components.kt @@ -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( ) @Composable -fun OptionsCard(card: OptionsCard, callbacks: Callbacks) { +fun ColumnScope.OptionsCard(card: OptionsCardData, callbacks: Callbacks) { 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 TitledRadioButton(option: RadioOption, 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 { diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/components/RadioButton.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/components/RadioButton.kt index 0b526326a1..602bfde0eb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/components/RadioButton.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/components/RadioButton.kt @@ -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 TitledRadioButton( + modifier: Modifier = Modifier, + option: RadioOption, + 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( + 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( + 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( + value = ExpiryType.AFTER_SEND.mode(7.days), + title = GetString(7.days), + subtitle = GetString("This is a subtitle"), + enabled = true, + selected = false + ) + ) {} + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/ui/theme/ThemeColors.kt b/app/src/main/java/org/thoughtcrime/securesms/ui/theme/ThemeColors.kt index 099498c535..354e5ac215 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ui/theme/ThemeColors.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/ui/theme/ThemeColors.kt @@ -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) diff --git a/app/src/test/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModelTest.kt b/app/src/test/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModelTest.kt index a9df5d946e..8bffb4878a 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModelTest.kt +++ b/app/src/test/java/org/thoughtcrime/securesms/conversation/disappearingmessages/DisappearingMessagesViewModelTest.kt @@ -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),