From 0a35c4fffd80eab8149bf70feed695cc8e085df6 Mon Sep 17 00:00:00 2001 From: Serhii Ihnatiev <17492722+thats-bot@users.noreply.github.com> Date: Fri, 14 Jun 2024 08:54:20 +0200 Subject: [PATCH] [#1259] Add bubble message component MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [#1259] - Add bubble message component - Closes #1418 - Wrap history and send message fields in bubble style - Update changelog * Change bubble message component file name * Apply bubble design to SendConfirmationView * Use different design for sent and received history transactions * Simplify Send transaction recognition * Color bubble arrow in transparent receiver case --------- Co-authored-by: Serhii Ihnatiev Co-authored-by: Honza Rychnovský --- CHANGELOG.md | 5 +- .../ui/design/component/BubbleMessage.kt | 136 ++++++++++++++++++ .../ui/screen/account/view/HistoryView.kt | 51 +++++-- .../zcash/ui/screen/send/view/SendView.kt | 114 ++++++++++----- .../view/SendConfirmationView.kt | 14 +- 5 files changed, 268 insertions(+), 52 deletions(-) create mode 100644 ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/BubbleMessage.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index c5a6a338..a3afdcf7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ directly impact users rather than highlighting other key architectural updates.* ## [Unreleased] +### Added +- New bubble message style for the Send and Transaction history item text components +- Display all messages within the transaction history record when it is expanded + ## [1.1.1 (660)] - 2024-06-05 ### Added @@ -41,7 +45,6 @@ directly impact users rather than highlighting other key architectural updates.* ### Changed - The app dialog window has now a bit more rounded corners - A few more minor UI improvements -- Display all messages within transaction history record when it is expanded ## [1.0 (650)] - 2024-05-07 diff --git a/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/BubbleMessage.kt b/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/BubbleMessage.kt new file mode 100644 index 00000000..1fe1a663 --- /dev/null +++ b/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/BubbleMessage.kt @@ -0,0 +1,136 @@ +package co.electriccoin.zcash.ui.design.component + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.shape.GenericShape +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import co.electriccoin.zcash.ui.design.theme.ZcashTheme + +private const val ARROW_PADDING = 16 +private const val ARROW_WIDTH = 24 +private const val ARROW_HEIGHT = 8 +private const val COMPONENT_MIN_WIDTH = ARROW_WIDTH * 3 + +@Preview(showBackground = true) +@Composable +private fun BubbleWithTextPreview() { + ZcashTheme { + BubbleMessage(backgroundColor = ZcashTheme.colors.dividerColor) { + Text( + text = "TextTextTextText", + fontSize = 16.sp, + modifier = Modifier.padding(ZcashTheme.dimens.spacingDefault) + ) + } + } +} + +@Preview("Small content and left arrow preview", showBackground = true) +@Composable +private fun BubbleMinSizePreview() { + ZcashTheme { + BubbleMessage(arrowAlignment = BubbleArrowAlignment.BottomLeft) { + Text( + text = "T", + textAlign = TextAlign.Center, + fontSize = 16.sp, + modifier = Modifier.padding(ZcashTheme.dimens.spacingDefault) + ) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun BubbleWithTextFieldPreview() { + ZcashTheme { + Box(modifier = Modifier.fillMaxWidth()) { + BubbleMessage { + @OptIn(ExperimentalFoundationApi::class) + FormTextField( + value = "FormTextField", + onValueChange = {}, + modifier = Modifier.padding(ZcashTheme.dimens.spacingDefault), + withBorder = false + ) + } + } + } +} + +@Composable +fun BubbleMessage( + modifier: Modifier = Modifier, + backgroundColor: Color = Color.Transparent, + borderStroke: BorderStroke = BorderStroke(1.dp, ZcashTheme.colors.textFieldFrame), + arrowAlignment: BubbleArrowAlignment = BubbleArrowAlignment.BottomRight, + content: @Composable () -> Unit +) { + val shape = createBubbleShape(arrowAlignment) + Surface( + modifier = + modifier + .clip(shape) + .border(shape = shape, border = borderStroke) + .background(backgroundColor) + .sizeIn(minWidth = COMPONENT_MIN_WIDTH.dp) // prevent collapsing when content is too small + .padding(bottom = ARROW_HEIGHT.dp), // compensate component height to center content + color = backgroundColor + ) { + content() + } +} + +@Composable +private fun createBubbleShape(arrowAlignment: BubbleArrowAlignment): Shape { + val density = LocalDensity.current + return GenericShape { size, _ -> + with(density) { + val arrowWidth = ARROW_WIDTH.dp.toPx() + val arrowHeight = ARROW_HEIGHT.dp.toPx() + val arrowPadding = ARROW_PADDING.dp.toPx() + + moveTo(0f, 0f) + lineTo(size.width, 0f) + lineTo(size.width, size.height - arrowHeight) + + when (arrowAlignment) { + BubbleArrowAlignment.BottomLeft -> { + lineTo(arrowWidth + arrowPadding, size.height - arrowHeight) + lineTo(arrowPadding, size.height) + lineTo(arrowPadding, size.height - arrowHeight) + } + BubbleArrowAlignment.BottomRight -> { + lineTo(size.width - arrowPadding, size.height - arrowHeight) + lineTo(size.width - arrowPadding, size.height) + lineTo(size.width - (arrowWidth + arrowPadding), size.height - arrowHeight) + } + } + + lineTo(0f, size.height - arrowHeight) + close() + } + } +} + +enum class BubbleArrowAlignment { + BottomLeft, + BottomRight +} diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/account/view/HistoryView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/account/view/HistoryView.kt index c46f1e1e..5eff9441 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/account/view/HistoryView.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/account/view/HistoryView.kt @@ -5,9 +5,7 @@ package co.electriccoin.zcash.ui.screen.account.view import androidx.compose.animation.animateContentSize import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.border import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -39,6 +37,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import cash.z.ecc.android.sdk.fixture.TransactionOverviewFixture import cash.z.ecc.android.sdk.model.FirstClassByteArray import cash.z.ecc.android.sdk.model.TransactionOverview import cash.z.ecc.android.sdk.model.TransactionRecipient @@ -51,6 +50,8 @@ import co.electriccoin.zcash.ui.common.compose.SynchronizationStatus import co.electriccoin.zcash.ui.common.model.WalletRestoringState import co.electriccoin.zcash.ui.common.model.WalletSnapshot import co.electriccoin.zcash.ui.design.component.BlankSurface +import co.electriccoin.zcash.ui.design.component.BubbleArrowAlignment +import co.electriccoin.zcash.ui.design.component.BubbleMessage import co.electriccoin.zcash.ui.design.component.CircularMidProgressIndicator import co.electriccoin.zcash.ui.design.component.StyledBalance import co.electriccoin.zcash.ui.design.component.TextWithIcon @@ -241,7 +242,34 @@ private fun ComposableHistoryListItemPreview() { } @Composable +@Preview("History List Item Expanded") +private fun ComposableHistoryListItemExpandedPreview() { + ZcashTheme(forceDarkMode = false) { + BlankSurface { + Column { + HistoryItem( + onAction = {}, + transaction = + TransactionUiFixture.new( + overview = TransactionOverviewFixture.new().copy(isSentTransaction = true), + expandableState = TrxItemState.EXPANDED + ) + ) + HistoryItem( + onAction = {}, + transaction = + TransactionUiFixture.new( + overview = TransactionOverviewFixture.new().copy(isSentTransaction = false), + expandableState = TrxItemState.EXPANDED + ) + ) + } + } + } +} + @Preview("Multiple History List Items") +@Composable private fun ComposableHistoryListItemsPreview() { ZcashTheme(forceDarkMode = false) { BlankSurface { @@ -596,7 +624,6 @@ private fun HistoryItemExpandedPart( state = transaction.overview.getExtendedState(), onAction = onAction ) - Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault)) } } else if (transaction.recipientAddressType == null || @@ -802,11 +829,17 @@ private fun HistoryItemMessagePart( } Column(modifier = modifier.then(Modifier.fillMaxWidth())) { - Box( - modifier = - Modifier - .fillMaxWidth() - .border(width = 1.dp, color = ZcashTheme.colors.textFieldFrame) + val (messageBackground, arrowAlignment) = + if (state.isSendType()) { + Color.Transparent to BubbleArrowAlignment.BottomLeft + } else { + ZcashTheme.colors.dividerColor to BubbleArrowAlignment.BottomRight + } + + BubbleMessage( + modifier = Modifier.fillMaxWidth(), + backgroundColor = messageBackground, + arrowAlignment = arrowAlignment ) { Text( text = message, @@ -854,6 +887,8 @@ internal enum class TransactionExtendedState { RECEIVE_FAILED; fun isFailed(): Boolean = this == SEND_FAILED || this == RECEIVE_FAILED + + fun isSendType(): Boolean = this == SENDING || this == SENT || this == SEND_FAILED } private fun TransactionOverview.getExtendedState(): TransactionExtendedState { diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/view/SendView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/view/SendView.kt index 6df72b73..92c75baf 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/view/SendView.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/view/SendView.kt @@ -32,6 +32,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusDirection import androidx.compose.ui.focus.FocusManager +import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.positionInRoot import androidx.compose.ui.platform.LocalContext @@ -69,6 +70,8 @@ import co.electriccoin.zcash.ui.design.component.BlankBgScaffold import co.electriccoin.zcash.ui.design.component.BlankSurface import co.electriccoin.zcash.ui.design.component.Body import co.electriccoin.zcash.ui.design.component.BodySmall +import co.electriccoin.zcash.ui.design.component.BubbleArrowAlignment +import co.electriccoin.zcash.ui.design.component.BubbleMessage import co.electriccoin.zcash.ui.design.component.FormTextField import co.electriccoin.zcash.ui.design.component.PrimaryButton import co.electriccoin.zcash.ui.design.component.Small @@ -110,6 +113,36 @@ private fun PreviewSendForm() { } } +@Composable +@Preview +private fun SendFormTransparentAddressPreview() { + ZcashTheme(forceDarkMode = false) { + Send( + sendStage = SendStage.Form, + onCreateZecSend = {}, + focusManager = LocalFocusManager.current, + onBack = {}, + onSettings = {}, + onQrScannerOpen = {}, + goBalances = {}, + hasCameraFeature = true, + recipientAddressState = + RecipientAddressState( + address = "tmCxJG72RWN66xwPtNgu4iKHpyysGrc7rEg", + type = AddressType.Transparent + ), + onRecipientAddressChange = {}, + setAmountState = {}, + amountState = AmountState.Valid(ZatoshiFixture.ZATOSHI_LONG.toString(), ZatoshiFixture.new()), + setMemoState = {}, + memoState = MemoState.new("Test message"), + topAppBarSubTitleState = TopAppBarSubTitleState.None, + walletSnapshot = WalletSnapshotFixture.new(), + balanceState = BalanceStateFixture.new() + ) + } +} + // TODO [#1260]: Cover Send screens UI with tests // TODO [#1260]: https://github.com/Electric-Coin-Company/zashi-android/issues/1260 @@ -618,8 +651,6 @@ fun SendFormAmountTextField( } } -// TODO [#1259]: Send.Form screen Memo field stroke bubble style -// TODO [#1259]: https://github.com/Electric-Coin-Company/zashi-android/issues/1259 @OptIn(ExperimentalFoundationApi::class) @Suppress("LongMethod", "LongParameterList") @Composable @@ -670,45 +701,56 @@ fun SendFormMemoTextField( Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall)) - FormTextField( - enabled = isMemoFieldAvailable, - value = + BubbleMessage( + arrowAlignment = BubbleArrowAlignment.BottomLeft, + backgroundColor = if (isMemoFieldAvailable) { - memoState.text + Color.Transparent } else { - "" + ZcashTheme.colors.textDisabled + } + ) { + FormTextField( + enabled = isMemoFieldAvailable, + value = + if (isMemoFieldAvailable) { + memoState.text + } else { + "" + }, + onValueChange = { + setMemoState(MemoState.new(it)) }, - onValueChange = { - setMemoState(MemoState.new(it)) - }, - bringIntoViewRequester = bringIntoViewRequester, - keyboardOptions = - KeyboardOptions( - keyboardType = KeyboardType.Text, - imeAction = ImeAction.Done - ), - keyboardActions = - KeyboardActions( - onDone = { - focusManager.clearFocus(true) - // Scroll down to make sure the Send button is visible on small screens - if (scrollTo > 0) { - scope.launch { - scrollState.animateScrollTo(scrollTo) + bringIntoViewRequester = bringIntoViewRequester, + keyboardOptions = + KeyboardOptions( + keyboardType = KeyboardType.Text, + imeAction = ImeAction.Done + ), + keyboardActions = + KeyboardActions( + onDone = { + focusManager.clearFocus(true) + // Scroll down to make sure the Send button is visible on small screens + if (scrollTo > 0) { + scope.launch { + scrollState.animateScrollTo(scrollTo) + } } } - } - ), - placeholder = { - Text( - text = stringResource(id = R.string.send_memo_hint), - style = ZcashTheme.extendedTypography.textFieldHint, - color = ZcashTheme.colors.textFieldHint - ) - }, - modifier = Modifier.fillMaxWidth(), - minHeight = ZcashTheme.dimens.textFieldMemoPanelDefaultHeight, - ) + ), + placeholder = { + Text( + text = stringResource(id = R.string.send_memo_hint), + style = ZcashTheme.extendedTypography.textFieldHint, + color = ZcashTheme.colors.textFieldHint + ) + }, + modifier = Modifier.fillMaxWidth(), + minHeight = ZcashTheme.dimens.textFieldMemoPanelDefaultHeight, + withBorder = false + ) + } if (isMemoFieldAvailable) { Body( diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/sendconfirmation/view/SendConfirmationView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/sendconfirmation/view/SendConfirmationView.kt index 40d08d36..2ced0bcd 100644 --- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/sendconfirmation/view/SendConfirmationView.kt +++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/sendconfirmation/view/SendConfirmationView.kt @@ -3,7 +3,6 @@ package co.electriccoin.zcash.ui.screen.sendconfirmation.view import androidx.compose.foundation.Image -import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -25,6 +24,7 @@ import androidx.compose.material3.Text 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.graphics.vector.ImageVector import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource @@ -32,7 +32,6 @@ import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import cash.z.ecc.android.sdk.fixture.WalletAddressFixture import cash.z.ecc.android.sdk.model.FirstClassByteArray import cash.z.ecc.android.sdk.model.TransactionSubmitResult @@ -48,6 +47,8 @@ import co.electriccoin.zcash.ui.design.component.AppAlertDialog import co.electriccoin.zcash.ui.design.component.BlankBgScaffold import co.electriccoin.zcash.ui.design.component.BlankSurface import co.electriccoin.zcash.ui.design.component.Body +import co.electriccoin.zcash.ui.design.component.BubbleArrowAlignment +import co.electriccoin.zcash.ui.design.component.BubbleMessage import co.electriccoin.zcash.ui.design.component.PrimaryButton import co.electriccoin.zcash.ui.design.component.Small import co.electriccoin.zcash.ui.design.component.SmallTopAppBar @@ -284,11 +285,10 @@ private fun SendConfirmationContent( Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingTiny)) - Box( - modifier = - Modifier - .fillMaxWidth() - .border(width = 1.dp, color = ZcashTheme.colors.textFieldFrame) + BubbleMessage( + modifier = Modifier.fillMaxWidth(), + arrowAlignment = BubbleArrowAlignment.BottomLeft, + backgroundColor = Color.Transparent ) { Tiny( text = zecSend.memo.value,