[#1259] Add bubble message component
* [#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 <serhii.ihnatiev@ext.grandcentrix.net> Co-authored-by: Honza Rychnovský <rychnovsky.honza@gmail.com>
This commit is contained in:
parent
34e741e3b3
commit
0a35c4fffd
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue