[#1412] Receive Page split into horizontal pager
* ISSUE-1412 Receive Page split into horizontal pager * ISSUE-1412 Code cleanup * ISSUE-1412 Code cleanup * Resolve code analysis warnings * Improve vertical paddings - So the entire screen is scrollable as expected - This also moves us towards the newly updated screen design --------- Co-authored-by: Milan Cerovsky <milan.cerovsky@leeaf.life> Co-authored-by: Honza <rychnovsky.honza@gmail.com>
This commit is contained in:
parent
0a35c4fffd
commit
4cdb9f3024
|
@ -0,0 +1,129 @@
|
|||
@file:OptIn(ExperimentalFoundationApi::class)
|
||||
|
||||
package co.electriccoin.zcash.ui.design.component
|
||||
|
||||
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.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.pager.PagerState
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.TabRow
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PagerTabsPreview() {
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
BlankSurface {
|
||||
PagerTabs(
|
||||
pagerState = rememberPagerState { 2 },
|
||||
tabs = persistentListOf("First", "Second"),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PagerTabsDarkPreview() {
|
||||
ZcashTheme(forceDarkMode = true) {
|
||||
BlankSurface {
|
||||
PagerTabs(
|
||||
pagerState = rememberPagerState { 2 },
|
||||
tabs = persistentListOf("First", "Second"),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PagerTabs(
|
||||
pagerState: PagerState,
|
||||
tabs: ImmutableList<String>,
|
||||
modifier: Modifier = Modifier,
|
||||
coroutineScope: CoroutineScope = rememberCoroutineScope(),
|
||||
onTabSelected: (index: Int) -> Unit = {},
|
||||
) {
|
||||
TabRow(
|
||||
modifier =
|
||||
modifier
|
||||
.padding(horizontal = ZcashTheme.dimens.screenHorizontalSpacingBig)
|
||||
.border(ZcashTheme.dimens.spacingTiny, ZcashTheme.colors.layoutStroke),
|
||||
selectedTabIndex = pagerState.currentPage,
|
||||
divider = {},
|
||||
indicator = {},
|
||||
) {
|
||||
tabs.forEachIndexed { index, tab ->
|
||||
PagerTab(
|
||||
title = tab,
|
||||
selected = pagerState.currentPage == index,
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
onTabSelected(index)
|
||||
pagerState.animateScrollToPage(index)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PagerTab(
|
||||
title: String,
|
||||
selected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Tab(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth(),
|
||||
selected = selected,
|
||||
onClick = onClick,
|
||||
selectedContentColor = Color.Transparent,
|
||||
unselectedContentColor = ZcashTheme.colors.layoutStroke
|
||||
) {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.background(
|
||||
if (selected) Color.Transparent else ZcashTheme.colors.layoutStroke
|
||||
)
|
||||
.padding(vertical = ZcashTheme.dimens.spacingMid, horizontal = ZcashTheme.dimens.spacingXtiny),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = ZcashTheme.dimens.spacingXtiny),
|
||||
text = title,
|
||||
color = if (selected) ZcashTheme.colors.textCommon else MaterialTheme.colorScheme.onPrimary,
|
||||
style = ZcashTheme.extendedTypography.restoringTopAppBarStyle,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +1,20 @@
|
|||
package co.electriccoin.zcash.ui.screen.receive.view
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Icon
|
||||
|
@ -19,6 +23,8 @@ import androidx.compose.material3.SnackbarHost
|
|||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
@ -44,34 +50,17 @@ import co.electriccoin.zcash.ui.common.model.VersionInfo
|
|||
import co.electriccoin.zcash.ui.common.test.CommonTag
|
||||
import co.electriccoin.zcash.ui.design.component.BlankBgScaffold
|
||||
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
|
||||
import co.electriccoin.zcash.ui.design.component.PagerTabs
|
||||
import co.electriccoin.zcash.ui.design.component.Reference
|
||||
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
|
||||
import co.electriccoin.zcash.ui.design.component.SubHeader
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.fixture.VersionInfoFixture
|
||||
import co.electriccoin.zcash.ui.screen.receive.util.AndroidQrCodeImageGenerator
|
||||
import co.electriccoin.zcash.ui.screen.receive.util.JvmQrCodeGenerator
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@Preview("Receive")
|
||||
@Composable
|
||||
private fun ComposablePreview() {
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
Receive(
|
||||
screenBrightnessState = ScreenBrightnessState.NORMAL,
|
||||
walletAddress = runBlocking { WalletAddressesFixture.new() },
|
||||
snackbarHostState = SnackbarHostState(),
|
||||
onSettings = {},
|
||||
onAdjustBrightness = {},
|
||||
onAddrCopyToClipboard = {},
|
||||
onQrImageShare = {},
|
||||
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
||||
versionInfo = VersionInfoFixture.new(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
fun Receive(
|
||||
|
@ -102,18 +91,16 @@ fun Receive(
|
|||
CircularScreenProgressIndicator()
|
||||
} else {
|
||||
ReceiveContents(
|
||||
walletAddress = walletAddress,
|
||||
walletAddresses = walletAddress,
|
||||
onAddressCopyToClipboard = onAddrCopyToClipboard,
|
||||
onQrImageShare = onQrImageShare,
|
||||
screenBrightnessState = screenBrightnessState,
|
||||
versionInfo = versionInfo,
|
||||
modifier =
|
||||
Modifier.padding(
|
||||
top = paddingValues.calculateTopPadding() + ZcashTheme.dimens.spacingDefault,
|
||||
bottom = paddingValues.calculateBottomPadding() + ZcashTheme.dimens.spacingDefault,
|
||||
start = ZcashTheme.dimens.screenHorizontalSpacingRegular,
|
||||
end = ZcashTheme.dimens.screenHorizontalSpacingRegular
|
||||
)
|
||||
top = paddingValues.calculateTopPadding()
|
||||
// We intentionally do not set the rest paddings, those are set by the underlying composable
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -160,154 +147,173 @@ private fun ReceiveTopAppBar(
|
|||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
private fun ReceiveContents(
|
||||
walletAddress: WalletAddresses,
|
||||
walletAddresses: WalletAddresses,
|
||||
onAddressCopyToClipboard: (String) -> Unit,
|
||||
onQrImageShare: (ImageBitmap) -> Unit,
|
||||
screenBrightnessState: ScreenBrightnessState,
|
||||
versionInfo: VersionInfo,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.verticalScroll(rememberScrollState())
|
||||
.then(modifier),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
if (screenBrightnessState == ScreenBrightnessState.FULL) {
|
||||
BrightenScreen()
|
||||
DisableScreenTimeout()
|
||||
}
|
||||
if (screenBrightnessState == ScreenBrightnessState.FULL) {
|
||||
BrightenScreen()
|
||||
DisableScreenTimeout()
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
||||
|
||||
Address(
|
||||
walletAddress = walletAddress.unified,
|
||||
onAddressCopyToClipboard = onAddressCopyToClipboard,
|
||||
onQrImageShare = onQrImageShare,
|
||||
)
|
||||
|
||||
if (versionInfo.isTestnet) {
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingHuge))
|
||||
|
||||
Address(
|
||||
walletAddress = walletAddress.sapling,
|
||||
onAddressCopyToClipboard = onAddressCopyToClipboard,
|
||||
onQrImageShare = onQrImageShare,
|
||||
val state by remember {
|
||||
derivedStateOf {
|
||||
listOfNotNull(
|
||||
walletAddresses.unified,
|
||||
walletAddresses.transparent,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingHuge))
|
||||
|
||||
Address(
|
||||
walletAddress = walletAddress.transparent,
|
||||
onAddressCopyToClipboard = onAddressCopyToClipboard,
|
||||
onQrImageShare = onQrImageShare,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val DEFAULT_QR_CODE_SIZE = 320.dp
|
||||
val pagerState = rememberPagerState { state.size }
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun Address(
|
||||
walletAddress: WalletAddress,
|
||||
onAddressCopyToClipboard: (String) -> Unit,
|
||||
onQrImageShare: (ImageBitmap) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
SubHeader(
|
||||
text =
|
||||
stringResource(
|
||||
id =
|
||||
when (walletAddress) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
) {
|
||||
Spacer(Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
|
||||
PagerTabs(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
pagerState = pagerState,
|
||||
tabs =
|
||||
state.map {
|
||||
stringResource(
|
||||
when (it) {
|
||||
is WalletAddress.Unified -> R.string.receive_wallet_address_unified
|
||||
is WalletAddress.Sapling -> R.string.receive_wallet_address_sapling
|
||||
is WalletAddress.Transparent -> R.string.receive_wallet_address_transparent
|
||||
}
|
||||
),
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
}.toPersistentList(),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingTiny))
|
||||
|
||||
val sizePixels = with(LocalDensity.current) { DEFAULT_QR_CODE_SIZE.toPx() }.roundToInt()
|
||||
val qrCodeImage =
|
||||
remember {
|
||||
qrCodeForAddress(
|
||||
address = walletAddress.address,
|
||||
size = sizePixels
|
||||
)
|
||||
}
|
||||
|
||||
QrCode(
|
||||
qrCodeImage = qrCodeImage,
|
||||
onQrImageBitmapShare = onQrImageShare,
|
||||
contentDescription =
|
||||
stringResource(
|
||||
id =
|
||||
when (walletAddress) {
|
||||
is WalletAddress.Unified -> R.string.receive_unified_content_description
|
||||
is WalletAddress.Sapling -> R.string.receive_sapling_content_description
|
||||
is WalletAddress.Transparent -> R.string.receive_transparent_content_description
|
||||
}
|
||||
),
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingTiny))
|
||||
|
||||
Text(
|
||||
text = walletAddress.address,
|
||||
style = ZcashTheme.extendedTypography.addressStyle,
|
||||
color = ZcashTheme.colors.textDescription,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier =
|
||||
Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.clickable { onAddressCopyToClipboard(walletAddress.address) }
|
||||
.padding(horizontal = ZcashTheme.dimens.spacingLarge)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Reference(
|
||||
text = stringResource(id = R.string.receive_copy),
|
||||
onClick = { onAddressCopyToClipboard(walletAddress.address) },
|
||||
textAlign = TextAlign.Center,
|
||||
imageVector = ImageVector.vectorResource(R.drawable.copy),
|
||||
imageContentDescription = null,
|
||||
modifier =
|
||||
Modifier
|
||||
.wrapContentSize()
|
||||
.padding(all = ZcashTheme.dimens.spacingDefault),
|
||||
)
|
||||
Reference(
|
||||
text = stringResource(id = R.string.receive_share),
|
||||
onClick = { onQrImageShare(qrCodeImage) },
|
||||
textAlign = TextAlign.Center,
|
||||
imageVector = ImageVector.vectorResource(R.drawable.share),
|
||||
imageContentDescription = null,
|
||||
modifier =
|
||||
Modifier
|
||||
.wrapContentSize()
|
||||
.padding(all = ZcashTheme.dimens.spacingDefault),
|
||||
HorizontalPager(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
state = pagerState,
|
||||
userScrollEnabled = false
|
||||
) { index ->
|
||||
AddressPage(
|
||||
walletAddresses = walletAddresses,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
walletAddress = state[index],
|
||||
versionInfo = versionInfo,
|
||||
onAddressCopyToClipboard = onAddressCopyToClipboard,
|
||||
onQrImageShare = onQrImageShare,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod", "LongParameterList")
|
||||
@Composable
|
||||
private fun AddressPage(
|
||||
walletAddresses: WalletAddresses,
|
||||
walletAddress: WalletAddress,
|
||||
versionInfo: VersionInfo,
|
||||
onAddressCopyToClipboard: (String) -> Unit,
|
||||
onQrImageShare: (ImageBitmap) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier =
|
||||
modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
.padding(
|
||||
horizontal = ZcashTheme.dimens.screenHorizontalSpacingRegular,
|
||||
vertical = ZcashTheme.dimens.spacingDefault,
|
||||
),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
QrCode(walletAddress, onAddressCopyToClipboard, onQrImageShare)
|
||||
|
||||
if (versionInfo.isTestnet && walletAddress is WalletAddress.Unified) {
|
||||
QrCode(walletAddresses.sapling, onAddressCopyToClipboard, onQrImageShare)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Suppress("LongMethod")
|
||||
private fun ColumnScope.QrCode(
|
||||
walletAddress: WalletAddress,
|
||||
onAddressCopyToClipboard: (String) -> Unit,
|
||||
onQrImageShare: (ImageBitmap) -> Unit,
|
||||
) {
|
||||
val sizePixels = with(LocalDensity.current) { DEFAULT_QR_CODE_SIZE.toPx() }.roundToInt()
|
||||
val qrCodeImage =
|
||||
remember {
|
||||
qrCodeForAddress(
|
||||
address = walletAddress.address,
|
||||
size = sizePixels
|
||||
)
|
||||
}
|
||||
|
||||
QrCode(
|
||||
qrCodeImage = qrCodeImage,
|
||||
onQrImageBitmapShare = onQrImageShare,
|
||||
contentDescription =
|
||||
stringResource(
|
||||
when (walletAddress) {
|
||||
is WalletAddress.Unified -> R.string.receive_unified_content_description
|
||||
is WalletAddress.Sapling -> R.string.receive_sapling_content_description
|
||||
is WalletAddress.Transparent -> R.string.receive_transparent_content_description
|
||||
}
|
||||
),
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingTiny))
|
||||
|
||||
Text(
|
||||
text = walletAddress.address,
|
||||
style = ZcashTheme.extendedTypography.addressStyle,
|
||||
color = ZcashTheme.colors.textDescription,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier =
|
||||
Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.clickable { onAddressCopyToClipboard(walletAddress.address) }
|
||||
.padding(horizontal = ZcashTheme.dimens.spacingLarge)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Reference(
|
||||
text = stringResource(id = R.string.receive_copy),
|
||||
onClick = { onAddressCopyToClipboard(walletAddress.address) },
|
||||
textAlign = TextAlign.Center,
|
||||
imageVector = ImageVector.vectorResource(R.drawable.copy),
|
||||
imageContentDescription = null,
|
||||
modifier =
|
||||
Modifier
|
||||
.wrapContentSize()
|
||||
.padding(all = ZcashTheme.dimens.spacingDefault),
|
||||
)
|
||||
Reference(
|
||||
text = stringResource(id = R.string.receive_share),
|
||||
onClick = { onQrImageShare(qrCodeImage) },
|
||||
textAlign = TextAlign.Center,
|
||||
imageVector = ImageVector.vectorResource(R.drawable.share),
|
||||
imageContentDescription = null,
|
||||
modifier =
|
||||
Modifier
|
||||
.wrapContentSize()
|
||||
.padding(all = ZcashTheme.dimens.spacingDefault),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun qrCodeForAddress(
|
||||
address: String,
|
||||
size: Int,
|
||||
|
@ -339,3 +345,22 @@ private fun QrCode(
|
|||
.then(modifier)
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun ComposablePreview() =
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
Receive(
|
||||
screenBrightnessState = ScreenBrightnessState.NORMAL,
|
||||
walletAddress = runBlocking { WalletAddressesFixture.new() },
|
||||
snackbarHostState = SnackbarHostState(),
|
||||
onSettings = {},
|
||||
onAdjustBrightness = {},
|
||||
onAddrCopyToClipboard = {},
|
||||
onQrImageShare = {},
|
||||
versionInfo = VersionInfoFixture.new(),
|
||||
topAppBarSubTitleState = TopAppBarSubTitleState.None,
|
||||
)
|
||||
}
|
||||
|
||||
private val DEFAULT_QR_CODE_SIZE = 320.dp
|
||||
|
|
Loading…
Reference in New Issue