[#789] Fix Send Screen Layout

* [#804][Design system] Paddings - Dimens

- Initial commit to ensure it meets our requirements
- This solution is now in parallel with a simpler existing Paddings solution
- Four spacing groups defined
- Provides a way to define default and custom spacing values
- Distinguished by screen size, but other metrics also available (e.g. orientation, aspect ratio, layout direction or screen shape)

* Fix spacing value

* Move spacings change logic to comment

- We've decided to have only one regular spacing group for now, which is suitable for most of current phone devices

* Move Dimens out of internal package

* Link TODO for later Paddings remove

* Link issue of Use Dimens across the app to TODO

* Add vertical scrolling feature to SendForm screen

* Add vertical scrolling feature to Settings screen

+ Switched from Paddings to Dimens spacing system

* Add paddings, vertical scrolling, move modifiers

- As part of this is also move modifiers to the caller side where it makes sense

* Improve Send screen layout

- Added header view to the screen
- Added spacers between some UI parts of the screen
- Fixed background colors
- And improved textfields sizes

* Link Fix ZEC balance number formatting issue

* Link Disable Memo UI field in case of Transparent address issue

* Replace all .dp with predefined Dimens spacing in SendView

* Create custom FormTextField UI component

* Re-write paddings to spacers in list
This commit is contained in:
Honza Rychnovsky 2023-03-16 15:36:06 +01:00 committed by GitHub
parent 5cfa3a99e3
commit d37310a935
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 151 additions and 64 deletions

View File

@ -17,10 +17,12 @@ import co.electriccoin.zcash.ui.design.theme.ZcashTheme
fun Header(
text: String,
modifier: Modifier = Modifier,
color: Color = ZcashTheme.colors.onBackgroundHeader
textAlign: TextAlign = TextAlign.Start,
color: Color = ZcashTheme.colors.onBackgroundHeader,
) {
Text(
text = text,
textAlign = textAlign,
style = MaterialTheme.typography.headlineLarge,
color = color,
modifier = modifier
@ -46,8 +48,8 @@ fun Body(
@Composable
fun Small(
text: String,
textAlign: TextAlign,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
textAlign: TextAlign = TextAlign.Start
) {
Text(
text = text,

View File

@ -0,0 +1,34 @@
package co.electriccoin.zcash.ui.design.component
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldColors
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.input.KeyboardType
@Suppress("LongParameterList")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun FormTextField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
label: @Composable (() -> Unit)? = null,
keyboardOptions: KeyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text),
colors: TextFieldColors = TextFieldDefaults.textFieldColors(
containerColor = Color.Transparent
)
) {
TextField(
value = value,
onValueChange = onValueChange,
label = label,
keyboardOptions = keyboardOptions,
colors = colors,
modifier = modifier
)
}

View File

@ -2,14 +2,15 @@ package co.electriccoin.zcash.ui.screen.send.view
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
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.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material3.ExperimentalMaterial3Api
@ -17,7 +18,6 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@ -29,8 +29,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
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.model.Memo
import cash.z.ecc.android.sdk.model.MonetarySeparators
import cash.z.ecc.android.sdk.model.Zatoshi
@ -42,10 +42,14 @@ import cash.z.ecc.android.sdk.model.toZecString
import cash.z.ecc.sdk.fixture.ZatoshiFixture
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT
import co.electriccoin.zcash.ui.design.component.Body
import co.electriccoin.zcash.ui.design.component.FormTextField
import co.electriccoin.zcash.ui.design.component.GradientSurface
import co.electriccoin.zcash.ui.design.component.Header
import co.electriccoin.zcash.ui.design.component.PrimaryButton
import co.electriccoin.zcash.ui.design.component.TimedButton
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.design.theme.ZcashTheme.dimens
import co.electriccoin.zcash.ui.screen.send.ext.ABBREVIATION_INDEX
import co.electriccoin.zcash.ui.screen.send.ext.Saver
import co.electriccoin.zcash.ui.screen.send.ext.abbreviated
@ -92,12 +96,21 @@ fun Send(
})
}) { paddingValues ->
SendMainContent(
paddingValues,
mySpendableBalance,
sendStage,
pressAndHoldInteractionSource,
setSendStage,
onCreateAndSend = onCreateAndSend
myBalance = mySpendableBalance,
sendStage = sendStage,
pressAndHoldInteractionSource = pressAndHoldInteractionSource,
setSendStage = setSendStage,
onCreateAndSend = onCreateAndSend,
modifier = Modifier
.verticalScroll(
rememberScrollState()
)
.padding(
top = paddingValues.calculateTopPadding() + dimens.spacingDefault,
bottom = dimens.spacingDefault,
start = dimens.spacingDefault,
end = dimens.spacingDefault
)
)
}
}
@ -123,32 +136,34 @@ private fun SendTopAppBar(onBack: () -> Unit) {
@Suppress("LongParameterList")
@Composable
private fun SendMainContent(
paddingValues: PaddingValues,
myBalance: Zatoshi,
sendStage: SendStage,
pressAndHoldInteractionSource: MutableInteractionSource,
setSendStage: (SendStage) -> Unit,
onCreateAndSend: (ZecSend) -> Unit
onCreateAndSend: (ZecSend) -> Unit,
modifier: Modifier = Modifier
) {
val (zecSend, setZecSend) = rememberSaveable(stateSaver = ZecSend.Saver) { mutableStateOf(null) }
if (sendStage == SendStage.Form || null == zecSend) {
SendForm(
paddingValues,
myBalance = myBalance,
previousZecSend = zecSend
) {
setSendStage(SendStage.Confirmation)
setZecSend(it)
}
previousZecSend = zecSend,
onCreateAndSend = {
setSendStage(SendStage.Confirmation)
setZecSend(it)
},
modifier = modifier
)
} else {
Confirmation(
paddingValues,
zecSend,
pressAndHoldInteractionSource
) {
onCreateAndSend(zecSend)
}
zecSend = zecSend,
pressAndHoldInteractionSource = pressAndHoldInteractionSource,
onConfirmation = {
onCreateAndSend(zecSend)
},
modifier = modifier
)
}
}
@ -159,15 +174,17 @@ private fun SendMainContent(
@Composable
@OptIn(ExperimentalMaterial3Api::class)
private fun SendForm(
paddingValues: PaddingValues,
myBalance: Zatoshi,
previousZecSend: ZecSend?,
onCreateAndSend: (ZecSend) -> Unit
onCreateAndSend: (ZecSend) -> Unit,
modifier: Modifier = Modifier
) {
val context = LocalContext.current
val monetarySeparators = MonetarySeparators.current()
val allowedCharacters = ZecString.allowedCharacters(monetarySeparators)
// TODO [#809]: Fix ZEC balance on Send screen
// TODO [#809]: https://github.com/zcash/secant-android-wallet/issues/809
var amountZecString by rememberSaveable {
mutableStateOf(previousZecSend?.amount?.toZecString() ?: "")
}
@ -181,41 +198,58 @@ private fun SendForm(
}
Column(
Modifier
modifier
.fillMaxHeight()
.padding(top = paddingValues.calculateTopPadding())
) {
Row(Modifier.fillMaxWidth()) {
Text(text = myBalance.toZecString())
}
Header(
text = stringResource(id = R.string.send_balance, myBalance.toZecString()),
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
Body(
text = stringResource(id = R.string.send_balance_subtitle),
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
TextField(
Spacer(modifier = Modifier.height(dimens.spacingLarge))
FormTextField(
value = amountZecString,
onValueChange = { newValue ->
if (!ZecStringExt.filterContinuous(context, monetarySeparators, newValue)) {
return@TextField
return@FormTextField
}
amountZecString = newValue.filter { allowedCharacters.contains(it) }
},
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
label = { Text(stringResource(id = R.string.send_amount)) }
label = { Text(stringResource(id = R.string.send_amount)) },
modifier = Modifier.fillMaxWidth()
)
Spacer(Modifier.size(8.dp))
Spacer(Modifier.size(dimens.spacingSmall))
TextField(
FormTextField(
value = recipientAddressString,
onValueChange = { recipientAddressString = it },
label = { Text(stringResource(id = R.string.send_to)) }
label = { Text(stringResource(id = R.string.send_to)) },
modifier = Modifier.fillMaxWidth()
)
Spacer(Modifier.size(8.dp))
Spacer(Modifier.size(dimens.spacingSmall))
TextField(value = memoString, onValueChange = {
if (Memo.isWithinMaxLength(it)) {
memoString = it
}
}, label = { Text(stringResource(id = R.string.send_memo)) })
// TODO [#810]: Disable Memo UI field in case of Transparent address
// TODO [#810]: https://github.com/zcash/secant-android-wallet/issues/810
FormTextField(
value = memoString,
onValueChange = {
if (Memo.isWithinMaxLength(it)) {
memoString = it
}
},
label = { Text(stringResource(id = R.string.send_memo)) },
modifier = Modifier.fillMaxWidth()
)
Spacer(Modifier.fillMaxHeight(MINIMAL_WEIGHT))
@ -225,9 +259,14 @@ private fun SendForm(
* without regard for RTL. This will get resolved once we do proper validation for
* the fields.
*/
Text(validation.joinToString(", "))
Text(
text = validation.joinToString(", "),
modifier = Modifier.fillMaxWidth()
)
}
Spacer(modifier = Modifier.height(dimens.spacingDefault))
PrimaryButton(
onClick = {
val zecSendValidation = ZecSendExt.new(
@ -253,15 +292,12 @@ private fun SendForm(
@Composable
private fun Confirmation(
paddingValues: PaddingValues,
zecSend: ZecSend,
pressAndHoldInteractionSource: MutableInteractionSource,
onConfirmation: () -> Unit
onConfirmation: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
Modifier
.padding(top = paddingValues.calculateTopPadding())
) {
Column(modifier) {
Text(
stringResource(
R.string.send_amount_and_address_format,

View File

@ -1,7 +1,11 @@
package co.electriccoin.zcash.ui.screen.settings.view
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.MoreVert
@ -25,7 +29,7 @@ import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.design.component.GradientSurface
import co.electriccoin.zcash.ui.design.component.SwitchWithLabel
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
import co.electriccoin.zcash.ui.design.theme.ZcashTheme.paddings
import co.electriccoin.zcash.ui.design.theme.ZcashTheme.dimens
@Preview("Settings")
@Composable
@ -75,10 +79,16 @@ fun Settings(
onBackgroundSyncSettingsChanged = onBackgroundSyncSettingsChanged,
onIsKeepScreenOnDuringSyncSettingsChanged = onIsKeepScreenOnDuringSyncSettingsChanged,
onAnalyticsSettingsChanged = onAnalyticsSettingsChanged,
modifier = Modifier.padding(
top = paddingValues.calculateTopPadding() + paddings.padding,
bottom = paddings.padding
)
modifier = Modifier
.verticalScroll(
rememberScrollState()
)
.padding(
top = paddingValues.calculateTopPadding() + dimens.spacingDefault,
bottom = dimens.spacingDefault,
start = dimens.spacingDefault,
end = dimens.spacingDefault
)
)
}
}
@ -150,20 +160,23 @@ private fun SettingsMainContent(
SwitchWithLabel(
label = stringResource(id = R.string.settings_enable_background_sync),
state = isBackgroundSyncEnabled,
onStateChange = { onBackgroundSyncSettingsChanged(!isBackgroundSyncEnabled) },
modifier = Modifier.padding(paddings.padding)
onStateChange = { onBackgroundSyncSettingsChanged(!isBackgroundSyncEnabled) }
)
Spacer(modifier = Modifier.height(dimens.spacingXlarge))
SwitchWithLabel(
label = stringResource(id = R.string.settings_enable_keep_screen_on),
state = isKeepScreenOnDuringSyncEnabled,
onStateChange = { onIsKeepScreenOnDuringSyncSettingsChanged(!isKeepScreenOnDuringSyncEnabled) },
modifier = Modifier.padding(paddings.padding)
onStateChange = { onIsKeepScreenOnDuringSyncSettingsChanged(!isKeepScreenOnDuringSyncEnabled) }
)
Spacer(modifier = Modifier.height(dimens.spacingXlarge))
SwitchWithLabel(
label = stringResource(id = R.string.settings_enable_analytics),
state = isAnalyticsEnabled,
onStateChange = { onAnalyticsSettingsChanged(!isAnalyticsEnabled) },
modifier = Modifier.padding(paddings.padding)
onStateChange = { onAnalyticsSettingsChanged(!isAnalyticsEnabled) }
)
}
}

View File

@ -2,6 +2,8 @@
<string name="send_title">Send ZEC</string>
<string name="send_back_content_description">Back</string>
<string name="send_scan_content_description">Scan</string>
<string name="send_balance" formatted="true"><xliff:g id="balance" example="12.345">%1$s</xliff:g> ZEC Available</string>
<string name="send_balance_subtitle">Additional funds may be in transit.</string>
<string name="send_to">Who would you like to send ZEC to?</string>
<string name="send_amount">How much?</string>
<string name="send_memo">Memo</string>