[#1079] Home screen bottom nav bar
* Changelog update * [#1118] Account screen Balance Text line break - Closes #1118 * [#1117] Reusable Loading screen indicator - Closes #1117 * [#1116] Balances screen structure - Closes #1116 * [#1079] Bottom bar - tabs navigation - Closes #1079 * [#1079] Rework UI tests * [#1079] File follow-ups * [#1079] Fix static lint tools warnings * [#1079] Improve Home sub-screens indexing * [#1079] Reorg navigation into Home components * [#1079] Align with Figma design * [#1079] Update screenshot and UI tests
This commit is contained in:
parent
94616c4a7a
commit
4cf608b733
|
@ -9,6 +9,9 @@ directly impact users rather than highlighting other key architectural updates.*
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Home screen navigation switched from the Side menu to the Bottom Navigation Tabs menu
|
||||||
|
|
||||||
## [0.2.0 (505)] - 2023-12-11
|
## [0.2.0 (505)] - 2023-12-11
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -3,4 +3,5 @@ package co.electriccoin.zcash.ui.design.component
|
||||||
object CommonTag {
|
object CommonTag {
|
||||||
const val CHIP_LAYOUT = "chip_layout"
|
const val CHIP_LAYOUT = "chip_layout"
|
||||||
const val CHIP = "chip"
|
const val CHIP = "chip"
|
||||||
|
const val TOP_APP_BAR = "top_app_bar"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
package co.electriccoin.zcash.ui.design.component
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun CircularScreenProgressIndicatorComposablePreview() {
|
||||||
|
ZcashTheme(forceDarkMode = false) {
|
||||||
|
GradientSurface {
|
||||||
|
CircularScreenProgressIndicator()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CircularScreenProgressIndicator(modifier: Modifier = Modifier) {
|
||||||
|
Box(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.then(modifier),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.width(ZcashTheme.dimens.circularScreenProgressWidth)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,9 @@
|
||||||
|
@file:Suppress("TooManyFunctions")
|
||||||
|
|
||||||
package co.electriccoin.zcash.ui.design.component
|
package co.electriccoin.zcash.ui.design.component
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.basicMarquee
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
@ -13,6 +17,7 @@ import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextDecoration
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
@ -178,6 +183,7 @@ fun Reference(
|
||||||
* @param amount of ZECs to be displayed
|
* @param amount of ZECs to be displayed
|
||||||
* @param modifier to modify the Text UI element as needed
|
* @param modifier to modify the Text UI element as needed
|
||||||
*/
|
*/
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun HeaderWithZecIcon(
|
fun HeaderWithZecIcon(
|
||||||
amount: String,
|
amount: String,
|
||||||
|
@ -187,7 +193,8 @@ fun HeaderWithZecIcon(
|
||||||
text = stringResource(R.string.amount_with_zec_currency_symbol, amount),
|
text = stringResource(R.string.amount_with_zec_currency_symbol, amount),
|
||||||
style = ZcashTheme.extendedTypography.zecBalance,
|
style = ZcashTheme.extendedTypography.zecBalance,
|
||||||
color = MaterialTheme.colorScheme.onBackground,
|
color = MaterialTheme.colorScheme.onBackground,
|
||||||
modifier = modifier
|
maxLines = 1,
|
||||||
|
modifier = Modifier.basicMarquee().then(modifier)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,3 +210,25 @@ fun BodyWithFiatCurrencySymbol(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NavigationTabText(
|
||||||
|
text: String,
|
||||||
|
selected: Boolean,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = ZcashTheme.extendedTypography.textNavTab,
|
||||||
|
fontWeight =
|
||||||
|
if (selected) {
|
||||||
|
FontWeight.Black
|
||||||
|
} else {
|
||||||
|
FontWeight.Normal
|
||||||
|
},
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Visible,
|
||||||
|
color = ZcashTheme.colors.tabTextColor,
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.wrapContentSize
|
import androidx.compose.foundation.layout.wrapContentSize
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.AccountCircle
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||||
|
@ -30,6 +31,7 @@ import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
@ -111,6 +113,31 @@ private fun TopAppBarHamburgerMenuComposablePreview() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun TopAppBarHamburgerPlusActionComposablePreview() {
|
||||||
|
ZcashTheme(forceDarkMode = false) {
|
||||||
|
GradientSurface {
|
||||||
|
SmallTopAppBar(
|
||||||
|
titleText = "Screen E",
|
||||||
|
hamburgerMenuActions = {
|
||||||
|
TopBarHamburgerMenuExample(
|
||||||
|
actionCallback = {}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
regularActions = {
|
||||||
|
IconButton(onClick = {}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.AccountCircle,
|
||||||
|
contentDescription = "Content description text"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun TopBarHamburgerMenuExample(
|
private fun TopBarHamburgerMenuExample(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
@ -236,7 +263,13 @@ fun SmallTopAppBar(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions = hamburgerMenuActions ?: regularActions ?: {},
|
actions = {
|
||||||
modifier = modifier
|
regularActions?.invoke(this)
|
||||||
|
hamburgerMenuActions?.invoke(this)
|
||||||
|
},
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.testTag(CommonTag.TOP_APP_BAR)
|
||||||
|
.then(modifier)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,8 @@ data class Dimens(
|
||||||
// Chip
|
// Chip
|
||||||
val chipShadowElevation: Dp,
|
val chipShadowElevation: Dp,
|
||||||
val chipStroke: Dp,
|
val chipStroke: Dp,
|
||||||
|
// Progress
|
||||||
|
val circularScreenProgressWidth: Dp,
|
||||||
// TopAppBar:
|
// TopAppBar:
|
||||||
val topAppBarZcashLogoHeight: Dp,
|
val topAppBarZcashLogoHeight: Dp,
|
||||||
val topAppBarActionRippleCorner: Dp,
|
val topAppBarActionRippleCorner: Dp,
|
||||||
|
@ -59,6 +61,7 @@ private val defaultDimens =
|
||||||
buttonHeight = 50.dp,
|
buttonHeight = 50.dp,
|
||||||
chipShadowElevation = 4.dp,
|
chipShadowElevation = 4.dp,
|
||||||
chipStroke = 0.5.dp,
|
chipStroke = 0.5.dp,
|
||||||
|
circularScreenProgressWidth = 48.dp,
|
||||||
topAppBarZcashLogoHeight = 24.dp,
|
topAppBarZcashLogoHeight = 24.dp,
|
||||||
topAppBarActionRippleCorner = 28.dp,
|
topAppBarActionRippleCorner = 28.dp,
|
||||||
textFieldDefaultHeight = 215.dp,
|
textFieldDefaultHeight = 215.dp,
|
||||||
|
|
|
@ -35,6 +35,9 @@ data class ExtendedColors(
|
||||||
val screenTitleColor: Color,
|
val screenTitleColor: Color,
|
||||||
val aboutTextColor: Color,
|
val aboutTextColor: Color,
|
||||||
val welcomeAnimationColor: Color,
|
val welcomeAnimationColor: Color,
|
||||||
|
val complementaryColor: Color,
|
||||||
|
val dividerColor: Color,
|
||||||
|
val tabTextColor: Color,
|
||||||
) {
|
) {
|
||||||
@Composable
|
@Composable
|
||||||
fun surfaceGradient() =
|
fun surfaceGradient() =
|
||||||
|
|
|
@ -11,6 +11,9 @@ import co.electriccoin.zcash.ui.design.theme.ExtendedColors
|
||||||
// TODO [#998]: Check and enhance screen dark mode
|
// TODO [#998]: Check and enhance screen dark mode
|
||||||
// TODO [#998]: https://github.com/Electric-Coin-Company/zashi-android/issues/998
|
// TODO [#998]: https://github.com/Electric-Coin-Company/zashi-android/issues/998
|
||||||
|
|
||||||
|
// TODO [#1091]: Clear unused color resources
|
||||||
|
// TODO [#1091]: https://github.com/Electric-Coin-Company/zashi-android/issues/1091
|
||||||
|
|
||||||
internal object Dark {
|
internal object Dark {
|
||||||
val backgroundStart = Color(0xFF000000)
|
val backgroundStart = Color(0xFF000000)
|
||||||
val backgroundEnd = Color(0xFF000000)
|
val backgroundEnd = Color(0xFF000000)
|
||||||
|
@ -65,12 +68,13 @@ internal object Dark {
|
||||||
|
|
||||||
val buttonShadowColor = Color(0xFFFFFFFF)
|
val buttonShadowColor = Color(0xFFFFFFFF)
|
||||||
|
|
||||||
// to be added later
|
// Proper values will be added later, see #998
|
||||||
val aboutTextColor = Color.Unspecified
|
val aboutTextColor = Color.Unspecified
|
||||||
|
|
||||||
val screenTitleColor = Color(0xFF040404)
|
val screenTitleColor = Color(0xFF040404)
|
||||||
|
|
||||||
val welcomeAnimationColor = Color(0xFF231F20)
|
val welcomeAnimationColor = Color(0xFF231F20)
|
||||||
|
val complementaryColor = Color(0xFFF4B728)
|
||||||
|
val dividerColor = Color(0xFFDDDDDD)
|
||||||
|
val tabTextColor = Color(0xFF040404)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal object Light {
|
internal object Light {
|
||||||
|
@ -131,10 +135,11 @@ internal object Light {
|
||||||
val buttonShadowColor = Color(0xFF000000)
|
val buttonShadowColor = Color(0xFF000000)
|
||||||
|
|
||||||
val screenTitleColor = Color(0xFF040404)
|
val screenTitleColor = Color(0xFF040404)
|
||||||
|
|
||||||
val aboutTextColor = Color(0xFF4E4E4E)
|
val aboutTextColor = Color(0xFF4E4E4E)
|
||||||
|
|
||||||
val welcomeAnimationColor = Color(0xFF231F20)
|
val welcomeAnimationColor = Color(0xFF231F20)
|
||||||
|
val complementaryColor = Color(0xFFF4B728)
|
||||||
|
val dividerColor = Color(0xFFDDDDDD)
|
||||||
|
val tabTextColor = Color(0xFF040404)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val DarkColorPalette =
|
internal val DarkColorPalette =
|
||||||
|
@ -189,7 +194,10 @@ internal val DarkExtendedColorPalette =
|
||||||
buttonShadowColor = Dark.buttonShadowColor,
|
buttonShadowColor = Dark.buttonShadowColor,
|
||||||
screenTitleColor = Dark.screenTitleColor,
|
screenTitleColor = Dark.screenTitleColor,
|
||||||
aboutTextColor = Dark.aboutTextColor,
|
aboutTextColor = Dark.aboutTextColor,
|
||||||
welcomeAnimationColor = Dark.welcomeAnimationColor
|
welcomeAnimationColor = Dark.welcomeAnimationColor,
|
||||||
|
complementaryColor = Dark.complementaryColor,
|
||||||
|
dividerColor = Dark.dividerColor,
|
||||||
|
tabTextColor = Dark.tabTextColor,
|
||||||
)
|
)
|
||||||
|
|
||||||
internal val LightExtendedColorPalette =
|
internal val LightExtendedColorPalette =
|
||||||
|
@ -220,7 +228,10 @@ internal val LightExtendedColorPalette =
|
||||||
buttonShadowColor = Light.buttonShadowColor,
|
buttonShadowColor = Light.buttonShadowColor,
|
||||||
screenTitleColor = Light.screenTitleColor,
|
screenTitleColor = Light.screenTitleColor,
|
||||||
aboutTextColor = Light.aboutTextColor,
|
aboutTextColor = Light.aboutTextColor,
|
||||||
welcomeAnimationColor = Light.welcomeAnimationColor
|
welcomeAnimationColor = Light.welcomeAnimationColor,
|
||||||
|
complementaryColor = Light.complementaryColor,
|
||||||
|
dividerColor = Light.dividerColor,
|
||||||
|
tabTextColor = Dark.tabTextColor,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Suppress("CompositionLocalAllowlist")
|
@Suppress("CompositionLocalAllowlist")
|
||||||
|
@ -254,5 +265,8 @@ internal val LocalExtendedColors =
|
||||||
screenTitleColor = Color.Unspecified,
|
screenTitleColor = Color.Unspecified,
|
||||||
aboutTextColor = Color.Unspecified,
|
aboutTextColor = Color.Unspecified,
|
||||||
welcomeAnimationColor = Color.Unspecified,
|
welcomeAnimationColor = Color.Unspecified,
|
||||||
|
complementaryColor = Color.Unspecified,
|
||||||
|
dividerColor = Color.Unspecified,
|
||||||
|
tabTextColor = Color.Unspecified
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.googlefonts.Font
|
import androidx.compose.ui.text.googlefonts.Font
|
||||||
import androidx.compose.ui.text.googlefonts.GoogleFont
|
import androidx.compose.ui.text.googlefonts.GoogleFont
|
||||||
import androidx.compose.ui.text.style.BaselineShift
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import co.electriccoin.zcash.ui.design.R
|
import co.electriccoin.zcash.ui.design.R
|
||||||
|
@ -129,6 +128,12 @@ internal val SecondaryTypography =
|
||||||
fontFamily = ArchivoFontFamily,
|
fontFamily = ArchivoFontFamily,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
fontSize = 16.sp
|
fontSize = 16.sp
|
||||||
|
),
|
||||||
|
labelMedium =
|
||||||
|
TextStyle(
|
||||||
|
fontFamily = ArchivoFontFamily,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 12.sp
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -140,7 +145,6 @@ data class Typography(
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class ExtendedTypography(
|
data class ExtendedTypography(
|
||||||
val chipIndex: TextStyle,
|
|
||||||
val listItem: TextStyle,
|
val listItem: TextStyle,
|
||||||
val zecBalance: TextStyle,
|
val zecBalance: TextStyle,
|
||||||
val aboutText: TextStyle,
|
val aboutText: TextStyle,
|
||||||
|
@ -150,6 +154,7 @@ data class ExtendedTypography(
|
||||||
val textFieldHint: TextStyle,
|
val textFieldHint: TextStyle,
|
||||||
val textFieldValue: TextStyle,
|
val textFieldValue: TextStyle,
|
||||||
val textFieldBirthday: TextStyle,
|
val textFieldBirthday: TextStyle,
|
||||||
|
val textNavTab: TextStyle,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Suppress("CompositionLocalAllowlist")
|
@Suppress("CompositionLocalAllowlist")
|
||||||
|
@ -165,12 +170,6 @@ val LocalTypographies =
|
||||||
val LocalExtendedTypography =
|
val LocalExtendedTypography =
|
||||||
staticCompositionLocalOf {
|
staticCompositionLocalOf {
|
||||||
ExtendedTypography(
|
ExtendedTypography(
|
||||||
chipIndex =
|
|
||||||
PrimaryTypography.bodyLarge.copy(
|
|
||||||
fontSize = 10.sp,
|
|
||||||
baselineShift = BaselineShift.Superscript,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
),
|
|
||||||
listItem =
|
listItem =
|
||||||
PrimaryTypography.bodyLarge.copy(
|
PrimaryTypography.bodyLarge.copy(
|
||||||
fontSize = 24.sp
|
fontSize = 24.sp
|
||||||
|
@ -209,5 +208,9 @@ val LocalExtendedTypography =
|
||||||
fontSize = 17.sp,
|
fontSize = 17.sp,
|
||||||
),
|
),
|
||||||
textFieldBirthday = SecondaryTypography.headlineMedium.copy(),
|
textFieldBirthday = SecondaryTypography.headlineMedium.copy(),
|
||||||
|
textNavTab =
|
||||||
|
SecondaryTypography.labelSmall.copy(
|
||||||
|
fontSize = 13.sp
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,11 +31,12 @@ android {
|
||||||
setOf(
|
setOf(
|
||||||
"src/main/res/ui/about",
|
"src/main/res/ui/about",
|
||||||
"src/main/res/ui/account",
|
"src/main/res/ui/account",
|
||||||
"src/main/res/ui/new_wallet_recovery",
|
"src/main/res/ui/balances",
|
||||||
"src/main/res/ui/common",
|
"src/main/res/ui/common",
|
||||||
"src/main/res/ui/export_data",
|
"src/main/res/ui/export_data",
|
||||||
"src/main/res/ui/history",
|
"src/main/res/ui/history",
|
||||||
"src/main/res/ui/home",
|
"src/main/res/ui/home",
|
||||||
|
"src/main/res/ui/new_wallet_recovery",
|
||||||
"src/main/res/ui/onboarding",
|
"src/main/res/ui/onboarding",
|
||||||
"src/main/res/ui/receive",
|
"src/main/res/ui/receive",
|
||||||
"src/main/res/ui/request",
|
"src/main/res/ui/request",
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
package co.electriccoin.zcash.ui.screen.account
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||||
|
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
|
||||||
|
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||||
|
import co.electriccoin.zcash.ui.screen.account.view.Account
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
|
class AccountTestSetup(
|
||||||
|
private val composeTestRule: ComposeContentTestRule,
|
||||||
|
private val walletSnapshot: WalletSnapshot,
|
||||||
|
private val isShowFiatConversion: Boolean
|
||||||
|
) {
|
||||||
|
private val onSettingsCount = AtomicInteger(0)
|
||||||
|
private val onReceiveCount = AtomicInteger(0)
|
||||||
|
private val onSendCount = AtomicInteger(0)
|
||||||
|
private val onHistoryCount = AtomicInteger(0)
|
||||||
|
|
||||||
|
fun getOnSettingsCount(): Int {
|
||||||
|
composeTestRule.waitForIdle()
|
||||||
|
return onSettingsCount.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getOnReceiveCount(): Int {
|
||||||
|
composeTestRule.waitForIdle()
|
||||||
|
return onReceiveCount.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getOnSendCount(): Int {
|
||||||
|
composeTestRule.waitForIdle()
|
||||||
|
return onSendCount.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getOnHistoryCount(): Int {
|
||||||
|
composeTestRule.waitForIdle()
|
||||||
|
return onHistoryCount.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getWalletSnapshot(): WalletSnapshot {
|
||||||
|
composeTestRule.waitForIdle()
|
||||||
|
return walletSnapshot
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Suppress("TestFunctionName")
|
||||||
|
fun DefaultContent() {
|
||||||
|
Account(
|
||||||
|
walletSnapshot,
|
||||||
|
isUpdateAvailable = false,
|
||||||
|
isKeepScreenOnDuringSync = false,
|
||||||
|
isFiatConversionEnabled = isShowFiatConversion,
|
||||||
|
goSettings = {
|
||||||
|
onSettingsCount.incrementAndGet()
|
||||||
|
},
|
||||||
|
goHistory = {
|
||||||
|
onHistoryCount.incrementAndGet()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDefaultContent() {
|
||||||
|
composeTestRule.setContent {
|
||||||
|
ZcashTheme {
|
||||||
|
DefaultContent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package co.electriccoin.zcash.ui.screen.account.integration
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.assertIsDisplayed
|
||||||
|
import androidx.compose.ui.test.assertWidthIsAtLeast
|
||||||
|
import androidx.compose.ui.test.junit4.StateRestorationTester
|
||||||
|
import androidx.compose.ui.test.junit4.createComposeRule
|
||||||
|
import androidx.compose.ui.test.onNodeWithTag
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.test.filters.MediumTest
|
||||||
|
import cash.z.ecc.android.sdk.Synchronizer
|
||||||
|
import cash.z.ecc.android.sdk.model.PercentDecimal
|
||||||
|
import co.electriccoin.zcash.test.UiTestPrerequisites
|
||||||
|
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
|
||||||
|
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
|
||||||
|
import co.electriccoin.zcash.ui.screen.account.AccountTag
|
||||||
|
import co.electriccoin.zcash.ui.screen.account.AccountTestSetup
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertNotEquals
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class AccountViewIntegrationTest : UiTestPrerequisites() {
|
||||||
|
@get:Rule
|
||||||
|
val composeTestRule = createComposeRule()
|
||||||
|
|
||||||
|
private fun newTestSetup(walletSnapshot: WalletSnapshot) =
|
||||||
|
AccountTestSetup(
|
||||||
|
composeTestRule,
|
||||||
|
walletSnapshot,
|
||||||
|
isShowFiatConversion = false
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is just basic sanity check that we still have UI set up as expected after the state restore
|
||||||
|
@Test
|
||||||
|
@MediumTest
|
||||||
|
fun wallet_snapshot_restoration() {
|
||||||
|
val restorationTester = StateRestorationTester(composeTestRule)
|
||||||
|
val walletSnapshot =
|
||||||
|
WalletSnapshotFixture.new(
|
||||||
|
status = Synchronizer.Status.SYNCING,
|
||||||
|
progress = PercentDecimal(0.5f)
|
||||||
|
)
|
||||||
|
val testSetup = newTestSetup(walletSnapshot)
|
||||||
|
|
||||||
|
restorationTester.setContent {
|
||||||
|
testSetup.DefaultContent()
|
||||||
|
}
|
||||||
|
|
||||||
|
assertNotEquals(WalletSnapshotFixture.STATUS, testSetup.getWalletSnapshot().status)
|
||||||
|
assertEquals(Synchronizer.Status.SYNCING, testSetup.getWalletSnapshot().status)
|
||||||
|
|
||||||
|
assertNotEquals(WalletSnapshotFixture.PROGRESS, testSetup.getWalletSnapshot().progress)
|
||||||
|
assertEquals(0.5f, testSetup.getWalletSnapshot().progress.decimal)
|
||||||
|
|
||||||
|
restorationTester.emulateSavedInstanceStateRestore()
|
||||||
|
|
||||||
|
assertNotEquals(WalletSnapshotFixture.STATUS, testSetup.getWalletSnapshot().status)
|
||||||
|
assertEquals(Synchronizer.Status.SYNCING, testSetup.getWalletSnapshot().status)
|
||||||
|
|
||||||
|
assertNotEquals(WalletSnapshotFixture.PROGRESS, testSetup.getWalletSnapshot().progress)
|
||||||
|
assertEquals(0.5f, testSetup.getWalletSnapshot().progress.decimal)
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithTag(AccountTag.SINGLE_LINE_TEXT).also {
|
||||||
|
it.assertIsDisplayed()
|
||||||
|
it.assertWidthIsAtLeast(1.dp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package co.electriccoin.zcash.ui.screen.home.model
|
package co.electriccoin.zcash.ui.screen.account.model
|
||||||
|
|
||||||
import androidx.test.filters.SmallTest
|
import androidx.test.filters.SmallTest
|
||||||
import cash.z.ecc.android.sdk.Synchronizer
|
import cash.z.ecc.android.sdk.Synchronizer
|
||||||
|
@ -37,7 +37,7 @@ class WalletDisplayValuesTest {
|
||||||
assertNotNull(values)
|
assertNotNull(values)
|
||||||
assertEquals(1f, values.progress.decimal)
|
assertEquals(1f, values.progress.decimal)
|
||||||
assertEquals(walletSnapshot.totalBalance().toZecString(), values.zecAmountText)
|
assertEquals(walletSnapshot.totalBalance().toZecString(), values.zecAmountText)
|
||||||
assertTrue(values.statusText.startsWith(getStringResource(R.string.home_status_syncing_catchup)))
|
assertTrue(values.statusText.startsWith(getStringResource(R.string.account_status_syncing_catchup)))
|
||||||
// TODO [#578]: Provide Zatoshi -> USD fiat currency formatting
|
// TODO [#578]: Provide Zatoshi -> USD fiat currency formatting
|
||||||
// TODO [#578]: https://github.com/Electric-Coin-Company/zcash-android-wallet-sdk/issues/578
|
// TODO [#578]: https://github.com/Electric-Coin-Company/zcash-android-wallet-sdk/issues/578
|
||||||
assertEquals(FiatCurrencyConversionRateState.Unavailable, values.fiatCurrencyAmountState)
|
assertEquals(FiatCurrencyConversionRateState.Unavailable, values.fiatCurrencyAmountState)
|
|
@ -0,0 +1,102 @@
|
||||||
|
package co.electriccoin.zcash.ui.screen.account.view
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.assertIsDisplayed
|
||||||
|
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||||
|
import androidx.compose.ui.test.junit4.createComposeRule
|
||||||
|
import androidx.compose.ui.test.onNodeWithTag
|
||||||
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.compose.ui.test.performScrollTo
|
||||||
|
import androidx.test.filters.MediumTest
|
||||||
|
import co.electriccoin.zcash.test.UiTestPrerequisites
|
||||||
|
import co.electriccoin.zcash.ui.R
|
||||||
|
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
|
||||||
|
import co.electriccoin.zcash.ui.design.component.CommonTag
|
||||||
|
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
|
||||||
|
import co.electriccoin.zcash.ui.screen.account.AccountTag
|
||||||
|
import co.electriccoin.zcash.ui.screen.account.AccountTestSetup
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.clickSettingsTopAppBarMenu
|
||||||
|
import co.electriccoin.zcash.ui.test.getStringResource
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class AccountViewTest : UiTestPrerequisites() {
|
||||||
|
@get:Rule
|
||||||
|
val composeTestRule = createComposeRule()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@MediumTest
|
||||||
|
fun check_all_elementary_ui_elements_displayed() {
|
||||||
|
newTestSetup()
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithTag(CommonTag.TOP_APP_BAR)
|
||||||
|
.also {
|
||||||
|
it.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithTag(AccountTag.STATUS_VIEWS).also {
|
||||||
|
it.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithTag(AccountTag.FIAT_CONVERSION).also {
|
||||||
|
it.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithText(getStringResource(R.string.account_button_history), ignoreCase = true).also {
|
||||||
|
it.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@MediumTest
|
||||||
|
fun hide_fiat_conversion() {
|
||||||
|
newTestSetup(isShowFiatConversion = false)
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithTag(AccountTag.FIAT_CONVERSION).also {
|
||||||
|
it.assertDoesNotExist()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@MediumTest
|
||||||
|
fun click_history_button() {
|
||||||
|
val testSetup = newTestSetup()
|
||||||
|
|
||||||
|
Assert.assertEquals(0, testSetup.getOnHistoryCount())
|
||||||
|
|
||||||
|
composeTestRule.clickHistory()
|
||||||
|
|
||||||
|
Assert.assertEquals(1, testSetup.getOnHistoryCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@MediumTest
|
||||||
|
fun hamburger_settings_test() {
|
||||||
|
val testSetup = newTestSetup()
|
||||||
|
|
||||||
|
Assert.assertEquals(0, testSetup.getOnReceiveCount())
|
||||||
|
|
||||||
|
composeTestRule.clickSettingsTopAppBarMenu()
|
||||||
|
|
||||||
|
Assert.assertEquals(1, testSetup.getOnSettingsCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun newTestSetup(
|
||||||
|
isShowFiatConversion: Boolean = true,
|
||||||
|
walletSnapshot: WalletSnapshot = WalletSnapshotFixture.new()
|
||||||
|
) = AccountTestSetup(
|
||||||
|
composeTestRule,
|
||||||
|
walletSnapshot = walletSnapshot,
|
||||||
|
isShowFiatConversion = isShowFiatConversion
|
||||||
|
).apply {
|
||||||
|
setDefaultContent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ComposeContentTestRule.clickHistory() {
|
||||||
|
onNodeWithText(getStringResource(R.string.home_button_history), ignoreCase = true).also {
|
||||||
|
it.performScrollTo()
|
||||||
|
it.performClick()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,30 +1,19 @@
|
||||||
package co.electriccoin.zcash.ui.screen.home
|
package co.electriccoin.zcash.ui.screen.home
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||||
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
|
|
||||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
|
||||||
import co.electriccoin.zcash.ui.screen.home.view.Home
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
class HomeTestSetup(
|
class HomeTestSetup(
|
||||||
private val composeTestRule: ComposeContentTestRule,
|
private val composeTestRule: ComposeContentTestRule,
|
||||||
private val walletSnapshot: WalletSnapshot,
|
|
||||||
private val isShowFiatConversion: Boolean
|
|
||||||
) {
|
) {
|
||||||
private val onSettingsCount = AtomicInteger(0)
|
private val onAccountsCount = AtomicInteger(0)
|
||||||
private val onReceiveCount = AtomicInteger(0)
|
|
||||||
private val onSendCount = AtomicInteger(0)
|
private val onSendCount = AtomicInteger(0)
|
||||||
private val onHistoryCount = AtomicInteger(0)
|
private val onReceiveCount = AtomicInteger(0)
|
||||||
|
private val onBalancesCount = AtomicInteger(0)
|
||||||
|
|
||||||
fun getOnSettingsCount(): Int {
|
fun getOnAccountCount(): Int {
|
||||||
composeTestRule.waitForIdle()
|
composeTestRule.waitForIdle()
|
||||||
return onSettingsCount.get()
|
return onAccountsCount.get()
|
||||||
}
|
|
||||||
|
|
||||||
fun getOnReceiveCount(): Int {
|
|
||||||
composeTestRule.waitForIdle()
|
|
||||||
return onReceiveCount.get()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOnSendCount(): Int {
|
fun getOnSendCount(): Int {
|
||||||
|
@ -32,37 +21,27 @@ class HomeTestSetup(
|
||||||
return onSendCount.get()
|
return onSendCount.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOnHistoryCount(): Int {
|
fun getOnReceiveCount(): Int {
|
||||||
composeTestRule.waitForIdle()
|
composeTestRule.waitForIdle()
|
||||||
return onHistoryCount.get()
|
return onReceiveCount.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getWalletSnapshot(): WalletSnapshot {
|
fun getOnBalancesCount(): Int {
|
||||||
composeTestRule.waitForIdle()
|
composeTestRule.waitForIdle()
|
||||||
return walletSnapshot
|
return onBalancesCount.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO [#1125]: Home screen navigation: Add integration test
|
||||||
|
// TODO [#1125]: https://github.com/Electric-Coin-Company/zashi-android/issues/1125
|
||||||
|
|
||||||
|
// TODO [#1126]: Home screen view: Add view test
|
||||||
|
// TODO [#1126]: https://github.com/Electric-Coin-Company/zashi-android/issues/1126
|
||||||
|
|
||||||
|
/*
|
||||||
@Composable
|
@Composable
|
||||||
@Suppress("TestFunctionName")
|
@Suppress("TestFunctionName")
|
||||||
fun DefaultContent() {
|
fun DefaultContent() {
|
||||||
Home(
|
Home()
|
||||||
walletSnapshot,
|
|
||||||
isUpdateAvailable = false,
|
|
||||||
isKeepScreenOnDuringSync = false,
|
|
||||||
isFiatConversionEnabled = isShowFiatConversion,
|
|
||||||
goSettings = {
|
|
||||||
onSettingsCount.incrementAndGet()
|
|
||||||
},
|
|
||||||
goReceive = {
|
|
||||||
onReceiveCount.incrementAndGet()
|
|
||||||
},
|
|
||||||
goSend = {
|
|
||||||
onSendCount.incrementAndGet()
|
|
||||||
},
|
|
||||||
goHistory = {
|
|
||||||
onHistoryCount.incrementAndGet()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setDefaultContent() {
|
fun setDefaultContent() {
|
||||||
|
@ -72,4 +51,5 @@ class HomeTestSetup(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,17 @@
|
||||||
package co.electriccoin.zcash.ui.screen.home.integration
|
package co.electriccoin.zcash.ui.screen.home.integration
|
||||||
|
|
||||||
import androidx.compose.ui.test.assertIsDisplayed
|
|
||||||
import androidx.compose.ui.test.assertWidthIsAtLeast
|
|
||||||
import androidx.compose.ui.test.junit4.StateRestorationTester
|
|
||||||
import androidx.compose.ui.test.junit4.createComposeRule
|
import androidx.compose.ui.test.junit4.createComposeRule
|
||||||
import androidx.compose.ui.test.onNodeWithTag
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.test.filters.MediumTest
|
|
||||||
import cash.z.ecc.android.sdk.Synchronizer
|
|
||||||
import cash.z.ecc.android.sdk.model.PercentDecimal
|
|
||||||
import co.electriccoin.zcash.test.UiTestPrerequisites
|
import co.electriccoin.zcash.test.UiTestPrerequisites
|
||||||
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
|
|
||||||
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
|
|
||||||
import co.electriccoin.zcash.ui.screen.home.HomeTag
|
|
||||||
import co.electriccoin.zcash.ui.screen.home.HomeTestSetup
|
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
import org.junit.Assert.assertNotEquals
|
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
class HomeViewIntegrationTest : UiTestPrerequisites() {
|
class HomeViewIntegrationTest : UiTestPrerequisites() {
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val composeTestRule = createComposeRule()
|
val composeTestRule = createComposeRule()
|
||||||
|
|
||||||
|
// TODO [#1125]: Home screen navigation: Add integration test
|
||||||
|
// TODO [#1125]: https://github.com/Electric-Coin-Company/zashi-android/issues/1125
|
||||||
|
|
||||||
|
/*
|
||||||
private fun newTestSetup(walletSnapshot: WalletSnapshot) =
|
private fun newTestSetup(walletSnapshot: WalletSnapshot) =
|
||||||
HomeTestSetup(
|
HomeTestSetup(
|
||||||
composeTestRule,
|
composeTestRule,
|
||||||
|
@ -30,7 +19,6 @@ class HomeViewIntegrationTest : UiTestPrerequisites() {
|
||||||
isShowFiatConversion = false
|
isShowFiatConversion = false
|
||||||
)
|
)
|
||||||
|
|
||||||
// This is just basic sanity check that we still have UI set up as expected after the state restore
|
|
||||||
@Test
|
@Test
|
||||||
@MediumTest
|
@MediumTest
|
||||||
fun wallet_snapshot_restoration() {
|
fun wallet_snapshot_restoration() {
|
||||||
|
@ -60,9 +48,10 @@ class HomeViewIntegrationTest : UiTestPrerequisites() {
|
||||||
assertNotEquals(WalletSnapshotFixture.PROGRESS, testSetup.getWalletSnapshot().progress)
|
assertNotEquals(WalletSnapshotFixture.PROGRESS, testSetup.getWalletSnapshot().progress)
|
||||||
assertEquals(0.5f, testSetup.getWalletSnapshot().progress.decimal)
|
assertEquals(0.5f, testSetup.getWalletSnapshot().progress.decimal)
|
||||||
|
|
||||||
composeTestRule.onNodeWithTag(HomeTag.SINGLE_LINE_TEXT).also {
|
composeTestRule.onNodeWithTag(AccountTag.SINGLE_LINE_TEXT).also {
|
||||||
it.assertIsDisplayed()
|
it.assertIsDisplayed()
|
||||||
it.assertWidthIsAtLeast(1.dp)
|
it.assertWidthIsAtLeast(1.dp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,147 +1,40 @@
|
||||||
|
@file:Suppress("UnusedPrivateMember")
|
||||||
|
|
||||||
package co.electriccoin.zcash.ui.screen.home.view
|
package co.electriccoin.zcash.ui.screen.home.view
|
||||||
|
|
||||||
import androidx.compose.ui.test.assertIsDisplayed
|
|
||||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||||
import androidx.compose.ui.test.junit4.createComposeRule
|
|
||||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
|
||||||
import androidx.compose.ui.test.onNodeWithTag
|
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
import androidx.compose.ui.test.performScrollTo
|
import androidx.compose.ui.test.performScrollTo
|
||||||
import androidx.test.filters.MediumTest
|
|
||||||
import co.electriccoin.zcash.test.UiTestPrerequisites
|
|
||||||
import co.electriccoin.zcash.ui.R
|
import co.electriccoin.zcash.ui.R
|
||||||
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
|
|
||||||
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
|
|
||||||
import co.electriccoin.zcash.ui.screen.home.HomeTag
|
|
||||||
import co.electriccoin.zcash.ui.screen.home.HomeTestSetup
|
|
||||||
import co.electriccoin.zcash.ui.test.getStringResource
|
import co.electriccoin.zcash.ui.test.getStringResource
|
||||||
import org.junit.Assert.assertEquals
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
class HomeViewTest : UiTestPrerequisites() {
|
class HomeViewTest {
|
||||||
@get:Rule
|
// TODO [#1126]: Home screen view: Add view test
|
||||||
val composeTestRule = createComposeRule()
|
// TODO [#1126]: https://github.com/Electric-Coin-Company/zashi-android/issues/1126
|
||||||
|
|
||||||
@Test
|
|
||||||
@MediumTest
|
|
||||||
fun check_all_elementary_ui_elements_displayed() {
|
|
||||||
newTestSetup()
|
|
||||||
|
|
||||||
composeTestRule.onNodeWithContentDescription(getStringResource(R.string.home_menu_content_description)).also {
|
|
||||||
it.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
|
|
||||||
composeTestRule.onNodeWithTag(HomeTag.STATUS_VIEWS).also {
|
|
||||||
it.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
|
|
||||||
composeTestRule.onNodeWithTag(HomeTag.FIAT_CONVERSION).also {
|
|
||||||
it.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(getStringResource(R.string.home_button_send), ignoreCase = true).also {
|
|
||||||
it.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(getStringResource(R.string.home_button_receive), ignoreCase = true).also {
|
|
||||||
it.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@MediumTest
|
|
||||||
fun hide_fiat_conversion() {
|
|
||||||
newTestSetup(isShowFiatConversion = false)
|
|
||||||
|
|
||||||
composeTestRule.onNodeWithTag(HomeTag.FIAT_CONVERSION).also {
|
|
||||||
it.assertDoesNotExist()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@MediumTest
|
|
||||||
fun click_receive_button() {
|
|
||||||
val testSetup = newTestSetup()
|
|
||||||
|
|
||||||
assertEquals(0, testSetup.getOnReceiveCount())
|
|
||||||
|
|
||||||
composeTestRule.clickReceive()
|
|
||||||
|
|
||||||
assertEquals(1, testSetup.getOnReceiveCount())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@MediumTest
|
|
||||||
fun click_send_button() {
|
|
||||||
val testSetup = newTestSetup()
|
|
||||||
|
|
||||||
assertEquals(0, testSetup.getOnSendCount())
|
|
||||||
|
|
||||||
composeTestRule.clickSend()
|
|
||||||
|
|
||||||
assertEquals(1, testSetup.getOnSendCount())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@MediumTest
|
|
||||||
fun click_history_button() {
|
|
||||||
val testSetup = newTestSetup()
|
|
||||||
|
|
||||||
assertEquals(0, testSetup.getOnHistoryCount())
|
|
||||||
|
|
||||||
composeTestRule.clickHistory()
|
|
||||||
|
|
||||||
assertEquals(1, testSetup.getOnHistoryCount())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@MediumTest
|
|
||||||
fun hamburger_settings() {
|
|
||||||
val testSetup = newTestSetup()
|
|
||||||
|
|
||||||
assertEquals(0, testSetup.getOnReceiveCount())
|
|
||||||
|
|
||||||
composeTestRule.openSettings()
|
|
||||||
|
|
||||||
assertEquals(1, testSetup.getOnSettingsCount())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun newTestSetup(
|
|
||||||
isShowFiatConversion: Boolean = true,
|
|
||||||
walletSnapshot: WalletSnapshot = WalletSnapshotFixture.new()
|
|
||||||
) = HomeTestSetup(
|
|
||||||
composeTestRule,
|
|
||||||
walletSnapshot = walletSnapshot,
|
|
||||||
isShowFiatConversion = isShowFiatConversion
|
|
||||||
).apply {
|
|
||||||
setDefaultContent()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ComposeContentTestRule.openSettings() {
|
private fun ComposeContentTestRule.clickAccount() {
|
||||||
onNodeWithContentDescription(getStringResource(R.string.home_menu_content_description)).also {
|
onNodeWithText(getStringResource(R.string.home_tab_account), ignoreCase = true).also {
|
||||||
it.performClick()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ComposeContentTestRule.clickReceive() {
|
|
||||||
onNodeWithText(getStringResource(R.string.home_button_receive), ignoreCase = true).also {
|
|
||||||
it.performClick()
|
it.performClick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ComposeContentTestRule.clickSend() {
|
private fun ComposeContentTestRule.clickSend() {
|
||||||
onNodeWithText(getStringResource(R.string.home_button_send), ignoreCase = true).also {
|
onNodeWithText(getStringResource(R.string.home_tab_send), ignoreCase = true).also {
|
||||||
it.performScrollTo()
|
it.performScrollTo()
|
||||||
it.performClick()
|
it.performClick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ComposeContentTestRule.clickHistory() {
|
private fun ComposeContentTestRule.clickReceive() {
|
||||||
onNodeWithText(getStringResource(R.string.home_button_history), ignoreCase = true).also {
|
onNodeWithText(getStringResource(R.string.home_tab_receive), ignoreCase = true).also {
|
||||||
it.performScrollTo()
|
it.performClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ComposeContentTestRule.clickBalances() {
|
||||||
|
onNodeWithText(getStringResource(R.string.home_tab_balances), ignoreCase = true).also {
|
||||||
it.performClick()
|
it.performClick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,19 +38,19 @@ class ReceiveViewTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@MediumTest
|
@MediumTest
|
||||||
fun back() =
|
fun click_settings_test() =
|
||||||
runTest {
|
runTest {
|
||||||
val testSetup = newTestSetup(WalletAddressFixture.unified())
|
val testSetup = newTestSetup(WalletAddressFixture.unified())
|
||||||
|
|
||||||
assertEquals(0, testSetup.getOnBackCount())
|
assertEquals(0, testSetup.getOnSettingsCount())
|
||||||
|
|
||||||
composeTestRule.onNodeWithContentDescription(
|
composeTestRule.onNodeWithContentDescription(
|
||||||
getStringResource(R.string.receive_back_content_description)
|
getStringResource(R.string.settings_menu_content_description)
|
||||||
).also {
|
).also {
|
||||||
it.performClick()
|
it.performClick()
|
||||||
}
|
}
|
||||||
|
|
||||||
assertEquals(1, testSetup.getOnBackCount())
|
assertEquals(1, testSetup.getOnSettingsCount())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -19,7 +19,7 @@ class ReceiveViewTestSetup(
|
||||||
private val composeTestRule: ComposeContentTestRule,
|
private val composeTestRule: ComposeContentTestRule,
|
||||||
walletAddress: WalletAddress
|
walletAddress: WalletAddress
|
||||||
) {
|
) {
|
||||||
private val onBackCount = AtomicInteger(0)
|
private val onSettingsCount = AtomicInteger(0)
|
||||||
private val onAddressDetailsCount = AtomicInteger(0)
|
private val onAddressDetailsCount = AtomicInteger(0)
|
||||||
private val screenBrightness = ScreenBrightness()
|
private val screenBrightness = ScreenBrightness()
|
||||||
private val screenTimeout = ScreenTimeout()
|
private val screenTimeout = ScreenTimeout()
|
||||||
|
@ -34,9 +34,9 @@ class ReceiveViewTestSetup(
|
||||||
return onAdjustBrightness.get()
|
return onAdjustBrightness.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOnBackCount(): Int {
|
fun getOnSettingsCount(): Int {
|
||||||
composeTestRule.waitForIdle()
|
composeTestRule.waitForIdle()
|
||||||
return onBackCount.get()
|
return onSettingsCount.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOnAddressDetailsCount(): Int {
|
fun getOnAddressDetailsCount(): Int {
|
||||||
|
@ -54,8 +54,8 @@ class ReceiveViewTestSetup(
|
||||||
ZcashTheme {
|
ZcashTheme {
|
||||||
Receive(
|
Receive(
|
||||||
walletAddress,
|
walletAddress,
|
||||||
onBack = {
|
onSettings = {
|
||||||
onBackCount.getAndIncrement()
|
onSettingsCount.getAndIncrement()
|
||||||
},
|
},
|
||||||
onAddressDetails = {
|
onAddressDetails = {
|
||||||
onAddressDetailsCount.getAndIncrement()
|
onAddressDetailsCount.getAndIncrement()
|
||||||
|
|
|
@ -4,6 +4,7 @@ import androidx.compose.ui.test.assertIsEnabled
|
||||||
import androidx.compose.ui.test.assertIsNotEnabled
|
import androidx.compose.ui.test.assertIsNotEnabled
|
||||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
|
import androidx.compose.ui.test.onNodeWithTag
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
import androidx.compose.ui.test.performTextClearance
|
import androidx.compose.ui.test.performTextClearance
|
||||||
|
@ -20,6 +21,12 @@ internal fun ComposeContentTestRule.clickBack() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun ComposeContentTestRule.clickSettingsTopAppBarMenu() {
|
||||||
|
onNodeWithContentDescription(getStringResource(R.string.settings_menu_content_description)).also {
|
||||||
|
it.performClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal fun ComposeContentTestRule.clickScanner() {
|
internal fun ComposeContentTestRule.clickScanner() {
|
||||||
onNodeWithContentDescription(getStringResource(R.string.send_scan_content_description)).also {
|
onNodeWithContentDescription(getStringResource(R.string.send_scan_content_description)).also {
|
||||||
it.performClick()
|
it.performClick()
|
||||||
|
@ -70,25 +77,25 @@ internal fun ComposeContentTestRule.setMemo(memo: String) {
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ComposeContentTestRule.clickCreateAndSend() {
|
internal fun ComposeContentTestRule.clickCreateAndSend() {
|
||||||
onNodeWithText(getStringResource(R.string.send_create), ignoreCase = true).also {
|
onNodeWithTag(SendTag.SEND_FORM_BUTTON).also {
|
||||||
it.performClick()
|
it.performClick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ComposeContentTestRule.clickConfirmation() {
|
internal fun ComposeContentTestRule.clickConfirmation() {
|
||||||
onNodeWithText(getStringResource(R.string.send_confirmation_button), ignoreCase = true).also {
|
onNodeWithTag(SendTag.SEND_CONFIRMATION_BUTTON).also {
|
||||||
it.performClick()
|
it.performClick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ComposeContentTestRule.assertOnForm() {
|
internal fun ComposeContentTestRule.assertOnForm() {
|
||||||
onNodeWithText(getStringResource(R.string.send_create), ignoreCase = true).also {
|
onNodeWithTag(SendTag.SEND_FORM_BUTTON).also {
|
||||||
it.assertExists()
|
it.assertExists()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ComposeContentTestRule.assertOnConfirmation() {
|
internal fun ComposeContentTestRule.assertOnConfirmation() {
|
||||||
onNodeWithText(getStringResource(R.string.send_confirmation_button), ignoreCase = true).also {
|
onNodeWithTag(SendTag.SEND_CONFIRMATION_BUTTON).also {
|
||||||
it.assertExists()
|
it.assertExists()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,13 +119,13 @@ internal fun ComposeContentTestRule.assertOnSendFailure() {
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ComposeContentTestRule.assertSendEnabled() {
|
internal fun ComposeContentTestRule.assertSendEnabled() {
|
||||||
onNodeWithText(getStringResource(R.string.send_create), ignoreCase = true).also {
|
onNodeWithTag(SendTag.SEND_FORM_BUTTON).also {
|
||||||
it.assertIsEnabled()
|
it.assertIsEnabled()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun ComposeContentTestRule.assertSendDisabled() {
|
internal fun ComposeContentTestRule.assertSendDisabled() {
|
||||||
onNodeWithText(getStringResource(R.string.send_create), ignoreCase = true).also {
|
onNodeWithTag(SendTag.SEND_FORM_BUTTON).also {
|
||||||
it.assertIsNotEnabled()
|
it.assertIsNotEnabled()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ class SendViewTestSetup(
|
||||||
private val hasCameraFeature: Boolean
|
private val hasCameraFeature: Boolean
|
||||||
) {
|
) {
|
||||||
private val onBackCount = AtomicInteger(0)
|
private val onBackCount = AtomicInteger(0)
|
||||||
|
private val onSettingsCount = AtomicInteger(0)
|
||||||
private val onCreateCount = AtomicInteger(0)
|
private val onCreateCount = AtomicInteger(0)
|
||||||
private val onScannerCount = AtomicInteger(0)
|
private val onScannerCount = AtomicInteger(0)
|
||||||
val mutableActionExecuted = MutableStateFlow(false)
|
val mutableActionExecuted = MutableStateFlow(false)
|
||||||
|
@ -39,6 +40,11 @@ class SendViewTestSetup(
|
||||||
return onBackCount.get()
|
return onBackCount.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getOnSettingsCount(): Int {
|
||||||
|
composeTestRule.waitForIdle()
|
||||||
|
return onSettingsCount.get()
|
||||||
|
}
|
||||||
|
|
||||||
fun getOnCreateCount(): Int {
|
fun getOnCreateCount(): Int {
|
||||||
composeTestRule.waitForIdle()
|
composeTestRule.waitForIdle()
|
||||||
return onCreateCount.get()
|
return onCreateCount.get()
|
||||||
|
@ -96,6 +102,7 @@ class SendViewTestSetup(
|
||||||
zecSend = zecSend,
|
zecSend = zecSend,
|
||||||
onZecSendChange = setZecSend,
|
onZecSendChange = setZecSend,
|
||||||
onBack = onBackAction,
|
onBack = onBackAction,
|
||||||
|
onSettings = { onSettingsCount.incrementAndGet() },
|
||||||
onCreateAndSend = {
|
onCreateAndSend = {
|
||||||
onCreateCount.incrementAndGet()
|
onCreateCount.incrementAndGet()
|
||||||
lastZecSend = it
|
lastZecSend = it
|
||||||
|
|
|
@ -57,7 +57,8 @@ class SendViewIntegrationTest {
|
||||||
spendingKey = spendingKey,
|
spendingKey = spendingKey,
|
||||||
goToQrScanner = {},
|
goToQrScanner = {},
|
||||||
goBack = {},
|
goBack = {},
|
||||||
hasCameraFeature = true
|
hasCameraFeature = true,
|
||||||
|
goSettings = {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import androidx.compose.ui.test.assertIsNotEnabled
|
||||||
import androidx.compose.ui.test.assertTextEquals
|
import androidx.compose.ui.test.assertTextEquals
|
||||||
import androidx.compose.ui.test.junit4.createComposeRule
|
import androidx.compose.ui.test.junit4.createComposeRule
|
||||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
|
import androidx.compose.ui.test.onNodeWithTag
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
import androidx.test.filters.MediumTest
|
import androidx.test.filters.MediumTest
|
||||||
|
@ -18,6 +19,8 @@ import cash.z.ecc.sdk.fixture.ZecSendFixture
|
||||||
import co.electriccoin.zcash.test.UiTestPrerequisites
|
import co.electriccoin.zcash.test.UiTestPrerequisites
|
||||||
import co.electriccoin.zcash.ui.R
|
import co.electriccoin.zcash.ui.R
|
||||||
import co.electriccoin.zcash.ui.fixture.SendArgumentsWrapperFixture
|
import co.electriccoin.zcash.ui.fixture.SendArgumentsWrapperFixture
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.SendTag
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.SendTag.SEND_FAILED_BUTTON
|
||||||
import co.electriccoin.zcash.ui.screen.send.SendViewTestSetup
|
import co.electriccoin.zcash.ui.screen.send.SendViewTestSetup
|
||||||
import co.electriccoin.zcash.ui.screen.send.assertOnConfirmation
|
import co.electriccoin.zcash.ui.screen.send.assertOnConfirmation
|
||||||
import co.electriccoin.zcash.ui.screen.send.assertOnForm
|
import co.electriccoin.zcash.ui.screen.send.assertOnForm
|
||||||
|
@ -30,6 +33,7 @@ import co.electriccoin.zcash.ui.screen.send.clickBack
|
||||||
import co.electriccoin.zcash.ui.screen.send.clickConfirmation
|
import co.electriccoin.zcash.ui.screen.send.clickConfirmation
|
||||||
import co.electriccoin.zcash.ui.screen.send.clickCreateAndSend
|
import co.electriccoin.zcash.ui.screen.send.clickCreateAndSend
|
||||||
import co.electriccoin.zcash.ui.screen.send.clickScanner
|
import co.electriccoin.zcash.ui.screen.send.clickScanner
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.clickSettingsTopAppBarMenu
|
||||||
import co.electriccoin.zcash.ui.screen.send.model.SendArgumentsWrapper
|
import co.electriccoin.zcash.ui.screen.send.model.SendArgumentsWrapper
|
||||||
import co.electriccoin.zcash.ui.screen.send.model.SendStage
|
import co.electriccoin.zcash.ui.screen.send.model.SendStage
|
||||||
import co.electriccoin.zcash.ui.screen.send.setAmount
|
import co.electriccoin.zcash.ui.screen.send.setAmount
|
||||||
|
@ -38,7 +42,6 @@ import co.electriccoin.zcash.ui.screen.send.setValidAddress
|
||||||
import co.electriccoin.zcash.ui.screen.send.setValidAmount
|
import co.electriccoin.zcash.ui.screen.send.setValidAmount
|
||||||
import co.electriccoin.zcash.ui.screen.send.setValidMemo
|
import co.electriccoin.zcash.ui.screen.send.setValidMemo
|
||||||
import co.electriccoin.zcash.ui.test.getStringResource
|
import co.electriccoin.zcash.ui.test.getStringResource
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
@ -74,7 +77,7 @@ class SendViewTest : UiTestPrerequisites() {
|
||||||
@Suppress("UNUSED_VARIABLE")
|
@Suppress("UNUSED_VARIABLE")
|
||||||
val testSetup = newTestSetup()
|
val testSetup = newTestSetup()
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(getStringResource(R.string.send_create), ignoreCase = true).also {
|
composeTestRule.onNodeWithTag(SendTag.SEND_FORM_BUTTON).also {
|
||||||
it.assertExists()
|
it.assertExists()
|
||||||
it.assertIsNotEnabled()
|
it.assertIsNotEnabled()
|
||||||
}
|
}
|
||||||
|
@ -82,7 +85,6 @@ class SendViewTest : UiTestPrerequisites() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
fun create_request_no_memo() =
|
fun create_request_no_memo() =
|
||||||
runTest {
|
runTest {
|
||||||
val testSetup = newTestSetup()
|
val testSetup = newTestSetup()
|
||||||
|
@ -117,7 +119,6 @@ class SendViewTest : UiTestPrerequisites() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
fun create_request_with_memo() =
|
fun create_request_with_memo() =
|
||||||
runTest {
|
runTest {
|
||||||
val testSetup = newTestSetup()
|
val testSetup = newTestSetup()
|
||||||
|
@ -154,7 +155,6 @@ class SendViewTest : UiTestPrerequisites() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
fun check_regex_functionality_valid_inputs() =
|
fun check_regex_functionality_valid_inputs() =
|
||||||
runTest {
|
runTest {
|
||||||
val testSetup = newTestSetup()
|
val testSetup = newTestSetup()
|
||||||
|
@ -213,7 +213,6 @@ class SendViewTest : UiTestPrerequisites() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
fun check_regex_functionality_invalid_inputs() =
|
fun check_regex_functionality_invalid_inputs() =
|
||||||
runTest {
|
runTest {
|
||||||
val testSetup = newTestSetup()
|
val testSetup = newTestSetup()
|
||||||
|
@ -252,7 +251,6 @@ class SendViewTest : UiTestPrerequisites() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@MediumTest
|
@MediumTest
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
fun max_memo_length() =
|
fun max_memo_length() =
|
||||||
runTest {
|
runTest {
|
||||||
val testSetup = newTestSetup()
|
val testSetup = newTestSetup()
|
||||||
|
@ -294,14 +292,14 @@ class SendViewTest : UiTestPrerequisites() {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@MediumTest
|
@MediumTest
|
||||||
fun back_on_form() {
|
fun on_settings_click_test() {
|
||||||
val testSetup = newTestSetup()
|
val testSetup = newTestSetup()
|
||||||
|
|
||||||
assertEquals(0, testSetup.getOnBackCount())
|
assertEquals(0, testSetup.getOnSettingsCount())
|
||||||
|
|
||||||
composeTestRule.clickBack()
|
composeTestRule.clickSettingsTopAppBarMenu()
|
||||||
|
|
||||||
assertEquals(1, testSetup.getOnBackCount())
|
assertEquals(1, testSetup.getOnSettingsCount())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -405,7 +403,7 @@ class SendViewTest : UiTestPrerequisites() {
|
||||||
assertEquals(0, testSetup.getOnBackCount())
|
assertEquals(0, testSetup.getOnBackCount())
|
||||||
|
|
||||||
composeTestRule.assertOnSendFailure()
|
composeTestRule.assertOnSendFailure()
|
||||||
composeTestRule.onNodeWithText(getStringResource(R.string.send_failure_button), ignoreCase = true).also {
|
composeTestRule.onNodeWithTag(SEND_FAILED_BUTTON).also {
|
||||||
it.assertExists()
|
it.assertExists()
|
||||||
it.performClick()
|
it.performClick()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package co.electriccoin.zcash.ui
|
package co.electriccoin.zcash.ui
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
@ -9,7 +8,6 @@ import androidx.navigation.NavOptionsBuilder
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import co.electriccoin.zcash.spackle.Twig
|
|
||||||
import co.electriccoin.zcash.ui.NavigationArguments.SEND_AMOUNT
|
import co.electriccoin.zcash.ui.NavigationArguments.SEND_AMOUNT
|
||||||
import co.electriccoin.zcash.ui.NavigationArguments.SEND_MEMO
|
import co.electriccoin.zcash.ui.NavigationArguments.SEND_MEMO
|
||||||
import co.electriccoin.zcash.ui.NavigationArguments.SEND_RECIPIENT_ADDRESS
|
import co.electriccoin.zcash.ui.NavigationArguments.SEND_RECIPIENT_ADDRESS
|
||||||
|
@ -17,26 +15,22 @@ import co.electriccoin.zcash.ui.NavigationTargets.ABOUT
|
||||||
import co.electriccoin.zcash.ui.NavigationTargets.EXPORT_PRIVATE_DATA
|
import co.electriccoin.zcash.ui.NavigationTargets.EXPORT_PRIVATE_DATA
|
||||||
import co.electriccoin.zcash.ui.NavigationTargets.HISTORY
|
import co.electriccoin.zcash.ui.NavigationTargets.HISTORY
|
||||||
import co.electriccoin.zcash.ui.NavigationTargets.HOME
|
import co.electriccoin.zcash.ui.NavigationTargets.HOME
|
||||||
import co.electriccoin.zcash.ui.NavigationTargets.RECEIVE
|
|
||||||
import co.electriccoin.zcash.ui.NavigationTargets.REQUEST
|
import co.electriccoin.zcash.ui.NavigationTargets.REQUEST
|
||||||
import co.electriccoin.zcash.ui.NavigationTargets.SCAN
|
import co.electriccoin.zcash.ui.NavigationTargets.SCAN
|
||||||
import co.electriccoin.zcash.ui.NavigationTargets.SEED_RECOVERY
|
import co.electriccoin.zcash.ui.NavigationTargets.SEED_RECOVERY
|
||||||
import co.electriccoin.zcash.ui.NavigationTargets.SEND
|
|
||||||
import co.electriccoin.zcash.ui.NavigationTargets.SETTINGS
|
import co.electriccoin.zcash.ui.NavigationTargets.SETTINGS
|
||||||
import co.electriccoin.zcash.ui.NavigationTargets.SUPPORT
|
import co.electriccoin.zcash.ui.NavigationTargets.SUPPORT
|
||||||
import co.electriccoin.zcash.ui.NavigationTargets.WALLET_ADDRESS_DETAILS
|
import co.electriccoin.zcash.ui.NavigationTargets.WALLET_ADDRESS_DETAILS
|
||||||
import co.electriccoin.zcash.ui.configuration.ConfigurationEntries
|
import co.electriccoin.zcash.ui.configuration.ConfigurationEntries
|
||||||
import co.electriccoin.zcash.ui.configuration.RemoteConfig
|
import co.electriccoin.zcash.ui.configuration.RemoteConfig
|
||||||
import co.electriccoin.zcash.ui.screen.about.WrapAbout
|
import co.electriccoin.zcash.ui.screen.about.WrapAbout
|
||||||
import co.electriccoin.zcash.ui.screen.account.WrapAccount
|
|
||||||
import co.electriccoin.zcash.ui.screen.address.WrapWalletAddresses
|
import co.electriccoin.zcash.ui.screen.address.WrapWalletAddresses
|
||||||
import co.electriccoin.zcash.ui.screen.exportdata.WrapExportPrivateData
|
import co.electriccoin.zcash.ui.screen.exportdata.WrapExportPrivateData
|
||||||
import co.electriccoin.zcash.ui.screen.history.WrapHistory
|
import co.electriccoin.zcash.ui.screen.history.WrapHistory
|
||||||
import co.electriccoin.zcash.ui.screen.receive.WrapReceive
|
import co.electriccoin.zcash.ui.screen.home.WrapHome
|
||||||
import co.electriccoin.zcash.ui.screen.request.WrapRequest
|
import co.electriccoin.zcash.ui.screen.request.WrapRequest
|
||||||
import co.electriccoin.zcash.ui.screen.scan.WrapScanValidator
|
import co.electriccoin.zcash.ui.screen.scan.WrapScanValidator
|
||||||
import co.electriccoin.zcash.ui.screen.seedrecovery.WrapSeedRecovery
|
import co.electriccoin.zcash.ui.screen.seedrecovery.WrapSeedRecovery
|
||||||
import co.electriccoin.zcash.ui.screen.send.WrapSend
|
|
||||||
import co.electriccoin.zcash.ui.screen.send.model.SendArgumentsWrapper
|
import co.electriccoin.zcash.ui.screen.send.model.SendArgumentsWrapper
|
||||||
import co.electriccoin.zcash.ui.screen.settings.WrapSettings
|
import co.electriccoin.zcash.ui.screen.settings.WrapSettings
|
||||||
import co.electriccoin.zcash.ui.screen.support.WrapSupport
|
import co.electriccoin.zcash.ui.screen.support.WrapSupport
|
||||||
|
@ -48,19 +42,31 @@ internal fun MainActivity.Navigation() {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navController =
|
val navController =
|
||||||
rememberNavController().also {
|
rememberNavController().also {
|
||||||
// This suppress is necessary, as this is how we set up the nav controller for tests.
|
|
||||||
@SuppressLint("RestrictedApi")
|
|
||||||
navControllerForTesting = it
|
navControllerForTesting = it
|
||||||
}
|
}
|
||||||
|
|
||||||
NavHost(navController = navController, startDestination = HOME) {
|
NavHost(navController = navController, startDestination = HOME) {
|
||||||
composable(HOME) {
|
composable(HOME) { backStackEntry ->
|
||||||
WrapAccount(
|
WrapHome(
|
||||||
|
onPageChange = {
|
||||||
|
homeViewModel.screenIndex.value = it
|
||||||
|
},
|
||||||
|
goAddressDetails = { navController.navigateJustOnce(WALLET_ADDRESS_DETAILS) },
|
||||||
|
goBack = { finish() },
|
||||||
goHistory = { navController.navigateJustOnce(HISTORY) },
|
goHistory = { navController.navigateJustOnce(HISTORY) },
|
||||||
goReceive = { navController.navigateJustOnce(RECEIVE) },
|
|
||||||
goSend = { navController.navigateJustOnce(SEND) },
|
|
||||||
goSettings = { navController.navigateJustOnce(SETTINGS) },
|
goSettings = { navController.navigateJustOnce(SETTINGS) },
|
||||||
|
goScan = { navController.navigateJustOnce(SCAN) },
|
||||||
|
sendArgumentsWrapper =
|
||||||
|
SendArgumentsWrapper(
|
||||||
|
recipientAddress = backStackEntry.savedStateHandle[SEND_RECIPIENT_ADDRESS],
|
||||||
|
amount = backStackEntry.savedStateHandle[SEND_AMOUNT],
|
||||||
|
memo = backStackEntry.savedStateHandle[SEND_MEMO]
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
// Remove used Send screen parameters passed from the Scan screen if some exist
|
||||||
|
backStackEntry.savedStateHandle.remove<String>(SEND_RECIPIENT_ADDRESS)
|
||||||
|
backStackEntry.savedStateHandle.remove<String>(SEND_AMOUNT)
|
||||||
|
backStackEntry.savedStateHandle.remove<String>(SEND_MEMO)
|
||||||
|
|
||||||
if (ConfigurationEntries.IS_APP_UPDATE_CHECK_ENABLED.getValue(RemoteConfig.current)) {
|
if (ConfigurationEntries.IS_APP_UPDATE_CHECK_ENABLED.getValue(RemoteConfig.current)) {
|
||||||
WrapCheckForUpdate()
|
WrapCheckForUpdate()
|
||||||
|
@ -112,33 +118,9 @@ internal fun MainActivity.Navigation() {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
composable(RECEIVE) {
|
|
||||||
WrapReceive(
|
|
||||||
onBack = { navController.popBackStackJustOnce(RECEIVE) },
|
|
||||||
onAddressDetails = { navController.navigateJustOnce(WALLET_ADDRESS_DETAILS) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
composable(REQUEST) {
|
composable(REQUEST) {
|
||||||
WrapRequest(goBack = { navController.popBackStackJustOnce(REQUEST) })
|
WrapRequest(goBack = { navController.popBackStackJustOnce(REQUEST) })
|
||||||
}
|
}
|
||||||
composable(SEND) { backStackEntry ->
|
|
||||||
WrapSend(
|
|
||||||
goToQrScanner = {
|
|
||||||
Twig.debug { "Opening Qr Scanner Screen" }
|
|
||||||
navController.navigateJustOnce(SCAN)
|
|
||||||
},
|
|
||||||
goBack = { navController.popBackStackJustOnce(SEND) },
|
|
||||||
sendArgumentsWrapper =
|
|
||||||
SendArgumentsWrapper(
|
|
||||||
recipientAddress = backStackEntry.savedStateHandle[SEND_RECIPIENT_ADDRESS],
|
|
||||||
amount = backStackEntry.savedStateHandle[SEND_AMOUNT],
|
|
||||||
memo = backStackEntry.savedStateHandle[SEND_MEMO]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
backStackEntry.savedStateHandle.remove<String>(SEND_RECIPIENT_ADDRESS)
|
|
||||||
backStackEntry.savedStateHandle.remove<String>(SEND_AMOUNT)
|
|
||||||
backStackEntry.savedStateHandle.remove<String>(SEND_MEMO)
|
|
||||||
}
|
|
||||||
composable(SUPPORT) {
|
composable(SUPPORT) {
|
||||||
// Pop back stack won't be right if we deep link into support
|
// Pop back stack won't be right if we deep link into support
|
||||||
WrapSupport(goBack = { navController.popBackStackJustOnce(SUPPORT) })
|
WrapSupport(goBack = { navController.popBackStackJustOnce(SUPPORT) })
|
||||||
|
@ -208,6 +190,7 @@ object NavigationArguments {
|
||||||
|
|
||||||
object NavigationTargets {
|
object NavigationTargets {
|
||||||
const val ABOUT = "about"
|
const val ABOUT = "about"
|
||||||
|
const val ACCOUNT = "account"
|
||||||
const val EXPORT_PRIVATE_DATA = "export_private_data"
|
const val EXPORT_PRIVATE_DATA = "export_private_data"
|
||||||
const val HISTORY = "history"
|
const val HISTORY = "history"
|
||||||
const val HOME = "home"
|
const val HOME = "home"
|
||||||
|
|
|
@ -5,4 +5,5 @@ package co.electriccoin.zcash.ui.common.test
|
||||||
*/
|
*/
|
||||||
object CommonTag {
|
object CommonTag {
|
||||||
const val WALLET_BIRTHDAY = "wallet_birthday"
|
const val WALLET_BIRTHDAY = "wallet_birthday"
|
||||||
|
const val SETTINGS_TOP_BAR_BUTTON = "settings_top_bar_button"
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@ import co.electriccoin.zcash.configuration.model.map.Configuration
|
||||||
import co.electriccoin.zcash.ui.common.ANDROID_STATE_FLOW_TIMEOUT
|
import co.electriccoin.zcash.ui.common.ANDROID_STATE_FLOW_TIMEOUT
|
||||||
import co.electriccoin.zcash.ui.preference.StandardPreferenceKeys
|
import co.electriccoin.zcash.ui.preference.StandardPreferenceKeys
|
||||||
import co.electriccoin.zcash.ui.preference.StandardPreferenceSingleton
|
import co.electriccoin.zcash.ui.preference.StandardPreferenceSingleton
|
||||||
|
import co.electriccoin.zcash.ui.screen.home.HomeScreenIndex
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.emitAll
|
import kotlinx.coroutines.flow.emitAll
|
||||||
|
@ -15,8 +17,14 @@ import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
|
||||||
class HomeViewModel(application: Application) : AndroidViewModel(application) {
|
class HomeViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
|
/**
|
||||||
|
* Current Home sub-screen index in flow.
|
||||||
|
*/
|
||||||
|
val screenIndex: MutableStateFlow<HomeScreenIndex> = MutableStateFlow(HomeScreenIndex.ACCOUNT)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A flow of whether background sync is enabled
|
* A flow of whether background sync is enabled
|
||||||
|
* Current Home sub-screen index in flow.
|
||||||
*/
|
*/
|
||||||
val isBackgroundSyncEnabled: StateFlow<Boolean?> =
|
val isBackgroundSyncEnabled: StateFlow<Boolean?> =
|
||||||
flow {
|
flow {
|
||||||
|
|
|
@ -57,7 +57,6 @@ import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
import kotlin.time.ExperimentalTime
|
|
||||||
|
|
||||||
// To make this more multiplatform compatible, we need to remove the dependency on Context
|
// To make this more multiplatform compatible, we need to remove the dependency on Context
|
||||||
// for loading the preferences.
|
// for loading the preferences.
|
||||||
|
@ -151,7 +150,7 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class, ExperimentalTime::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
val walletSnapshot: StateFlow<WalletSnapshot?> =
|
val walletSnapshot: StateFlow<WalletSnapshot?> =
|
||||||
synchronizer
|
synchronizer
|
||||||
.flatMapLatest {
|
.flatMapLatest {
|
||||||
|
|
|
@ -7,5 +7,4 @@ object AccountTag {
|
||||||
const val STATUS_VIEWS = "status_views"
|
const val STATUS_VIEWS = "status_views"
|
||||||
const val SINGLE_LINE_TEXT = "single_line_text"
|
const val SINGLE_LINE_TEXT = "single_line_text"
|
||||||
const val FIAT_CONVERSION = "fiat_conversion"
|
const val FIAT_CONVERSION = "fiat_conversion"
|
||||||
const val SETTINGS_TOP_BAR_BUTTON = "settings_top_bar_button"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,43 +6,23 @@ import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import co.electriccoin.zcash.ui.MainActivity
|
|
||||||
import co.electriccoin.zcash.ui.common.viewmodel.CheckUpdateViewModel
|
import co.electriccoin.zcash.ui.common.viewmodel.CheckUpdateViewModel
|
||||||
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
||||||
import co.electriccoin.zcash.ui.configuration.ConfigurationEntries
|
import co.electriccoin.zcash.ui.configuration.ConfigurationEntries
|
||||||
import co.electriccoin.zcash.ui.configuration.RemoteConfig
|
import co.electriccoin.zcash.ui.configuration.RemoteConfig
|
||||||
|
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
|
||||||
import co.electriccoin.zcash.ui.screen.account.view.Account
|
import co.electriccoin.zcash.ui.screen.account.view.Account
|
||||||
import co.electriccoin.zcash.ui.screen.settings.viewmodel.SettingsViewModel
|
import co.electriccoin.zcash.ui.screen.settings.viewmodel.SettingsViewModel
|
||||||
import co.electriccoin.zcash.ui.screen.update.AppUpdateCheckerImp
|
import co.electriccoin.zcash.ui.screen.update.AppUpdateCheckerImp
|
||||||
import co.electriccoin.zcash.ui.screen.update.model.UpdateState
|
import co.electriccoin.zcash.ui.screen.update.model.UpdateState
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Suppress("LongParameterList")
|
|
||||||
internal fun MainActivity.WrapAccount(
|
|
||||||
goSettings: () -> Unit,
|
|
||||||
goReceive: () -> Unit,
|
|
||||||
goSend: () -> Unit,
|
|
||||||
goHistory: () -> Unit
|
|
||||||
) {
|
|
||||||
WrapAccount(
|
|
||||||
this,
|
|
||||||
goSettings = goSettings,
|
|
||||||
goReceive = goReceive,
|
|
||||||
goSend = goSend,
|
|
||||||
goHistory = goHistory,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
@Suppress("LongParameterList")
|
|
||||||
internal fun WrapAccount(
|
internal fun WrapAccount(
|
||||||
activity: ComponentActivity,
|
activity: ComponentActivity,
|
||||||
goSettings: () -> Unit,
|
goSettings: () -> Unit,
|
||||||
goReceive: () -> Unit,
|
|
||||||
goSend: () -> Unit,
|
|
||||||
goHistory: () -> Unit,
|
goHistory: () -> Unit,
|
||||||
) {
|
) {
|
||||||
// we want to show information about app update, if available
|
// Show information about the app update, if available
|
||||||
val checkUpdateViewModel by activity.viewModels<CheckUpdateViewModel> {
|
val checkUpdateViewModel by activity.viewModels<CheckUpdateViewModel> {
|
||||||
CheckUpdateViewModel.CheckUpdateViewModelFactory(
|
CheckUpdateViewModel.CheckUpdateViewModelFactory(
|
||||||
activity.application,
|
activity.application,
|
||||||
|
@ -63,7 +43,8 @@ internal fun WrapAccount(
|
||||||
val isFiatConversionEnabled = ConfigurationEntries.IS_FIAT_CONVERSION_ENABLED.getValue(RemoteConfig.current)
|
val isFiatConversionEnabled = ConfigurationEntries.IS_FIAT_CONVERSION_ENABLED.getValue(RemoteConfig.current)
|
||||||
|
|
||||||
if (null == walletSnapshot) {
|
if (null == walletSnapshot) {
|
||||||
// Display loading indicator
|
// Improve this by allowing screen composition and updating it after the data is available
|
||||||
|
CircularScreenProgressIndicator()
|
||||||
} else {
|
} else {
|
||||||
Account(
|
Account(
|
||||||
walletSnapshot = walletSnapshot,
|
walletSnapshot = walletSnapshot,
|
||||||
|
@ -71,11 +52,10 @@ internal fun WrapAccount(
|
||||||
isKeepScreenOnDuringSync = isKeepScreenOnWhileSyncing,
|
isKeepScreenOnDuringSync = isKeepScreenOnWhileSyncing,
|
||||||
isFiatConversionEnabled = isFiatConversionEnabled,
|
isFiatConversionEnabled = isFiatConversionEnabled,
|
||||||
goSettings = goSettings,
|
goSettings = goSettings,
|
||||||
goReceive = goReceive,
|
|
||||||
goSend = goSend,
|
|
||||||
goHistory = goHistory
|
goHistory = goHistory
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// For benchmarking purposes
|
||||||
activity.reportFullyDrawn()
|
activity.reportFullyDrawn()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import cash.z.ecc.android.sdk.model.FiatCurrencyConversionRateState
|
||||||
import co.electriccoin.zcash.ui.R
|
import co.electriccoin.zcash.ui.R
|
||||||
import co.electriccoin.zcash.ui.common.DisableScreenTimeout
|
import co.electriccoin.zcash.ui.common.DisableScreenTimeout
|
||||||
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
|
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
|
||||||
|
import co.electriccoin.zcash.ui.common.test.CommonTag
|
||||||
import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT
|
import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT
|
||||||
import co.electriccoin.zcash.ui.design.component.Body
|
import co.electriccoin.zcash.ui.design.component.Body
|
||||||
import co.electriccoin.zcash.ui.design.component.BodyWithFiatCurrencySymbol
|
import co.electriccoin.zcash.ui.design.component.BodyWithFiatCurrencySymbol
|
||||||
|
@ -31,7 +32,6 @@ import co.electriccoin.zcash.ui.design.component.GradientSurface
|
||||||
import co.electriccoin.zcash.ui.design.component.HeaderWithZecIcon
|
import co.electriccoin.zcash.ui.design.component.HeaderWithZecIcon
|
||||||
import co.electriccoin.zcash.ui.design.component.PrimaryButton
|
import co.electriccoin.zcash.ui.design.component.PrimaryButton
|
||||||
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
|
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
|
||||||
import co.electriccoin.zcash.ui.design.component.TertiaryButton
|
|
||||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||||
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
|
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
|
||||||
import co.electriccoin.zcash.ui.screen.account.AccountTag
|
import co.electriccoin.zcash.ui.screen.account.AccountTag
|
||||||
|
@ -48,8 +48,6 @@ private fun ComposablePreview() {
|
||||||
isKeepScreenOnDuringSync = false,
|
isKeepScreenOnDuringSync = false,
|
||||||
isFiatConversionEnabled = false,
|
isFiatConversionEnabled = false,
|
||||||
goSettings = {},
|
goSettings = {},
|
||||||
goReceive = {},
|
|
||||||
goSend = {},
|
|
||||||
goHistory = {}
|
goHistory = {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -64,8 +62,6 @@ fun Account(
|
||||||
isKeepScreenOnDuringSync: Boolean?,
|
isKeepScreenOnDuringSync: Boolean?,
|
||||||
isFiatConversionEnabled: Boolean,
|
isFiatConversionEnabled: Boolean,
|
||||||
goSettings: () -> Unit,
|
goSettings: () -> Unit,
|
||||||
goReceive: () -> Unit,
|
|
||||||
goSend: () -> Unit,
|
|
||||||
goHistory: () -> Unit
|
goHistory: () -> Unit
|
||||||
) {
|
) {
|
||||||
Scaffold(topBar = {
|
Scaffold(topBar = {
|
||||||
|
@ -76,8 +72,6 @@ fun Account(
|
||||||
isUpdateAvailable = isUpdateAvailable,
|
isUpdateAvailable = isUpdateAvailable,
|
||||||
isKeepScreenOnDuringSync = isKeepScreenOnDuringSync,
|
isKeepScreenOnDuringSync = isKeepScreenOnDuringSync,
|
||||||
isFiatConversionEnabled = isFiatConversionEnabled,
|
isFiatConversionEnabled = isFiatConversionEnabled,
|
||||||
goReceive = goReceive,
|
|
||||||
goSend = goSend,
|
|
||||||
goHistory = goHistory,
|
goHistory = goHistory,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier.padding(
|
Modifier.padding(
|
||||||
|
@ -97,11 +91,11 @@ private fun AccountTopAppBar(onSettings: () -> Unit) {
|
||||||
hamburgerMenuActions = {
|
hamburgerMenuActions = {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onSettings,
|
onClick = onSettings,
|
||||||
modifier = Modifier.testTag(AccountTag.SETTINGS_TOP_BAR_BUTTON)
|
modifier = Modifier.testTag(CommonTag.SETTINGS_TOP_BAR_BUTTON)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(id = co.electriccoin.zcash.ui.design.R.drawable.hamburger_menu_icon),
|
painter = painterResource(id = co.electriccoin.zcash.ui.design.R.drawable.hamburger_menu_icon),
|
||||||
contentDescription = stringResource(id = R.string.account_menu_content_description)
|
contentDescription = stringResource(id = R.string.settings_menu_content_description)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,8 +109,6 @@ private fun AccountMainContent(
|
||||||
isUpdateAvailable: Boolean,
|
isUpdateAvailable: Boolean,
|
||||||
isKeepScreenOnDuringSync: Boolean?,
|
isKeepScreenOnDuringSync: Boolean?,
|
||||||
isFiatConversionEnabled: Boolean,
|
isFiatConversionEnabled: Boolean,
|
||||||
goReceive: () -> Unit,
|
|
||||||
goSend: () -> Unit,
|
|
||||||
goHistory: () -> Unit,
|
goHistory: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
|
@ -142,21 +134,7 @@ private fun AccountMainContent(
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
||||||
|
|
||||||
PrimaryButton(
|
PrimaryButton(onClick = goHistory, text = stringResource(R.string.account_button_history))
|
||||||
onClick = goSend,
|
|
||||||
text = stringResource(R.string.account_button_send)
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
|
||||||
|
|
||||||
PrimaryButton(
|
|
||||||
onClick = goReceive,
|
|
||||||
text = stringResource(R.string.account_button_receive)
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
|
||||||
|
|
||||||
TertiaryButton(onClick = goHistory, text = stringResource(R.string.account_button_history))
|
|
||||||
|
|
||||||
if (isKeepScreenOnDuringSync == true && walletSnapshot.status == Synchronizer.Status.SYNCING) {
|
if (isKeepScreenOnDuringSync == true && walletSnapshot.status == Synchronizer.Status.SYNCING) {
|
||||||
DisableScreenTimeout()
|
DisableScreenTimeout()
|
||||||
|
|
|
@ -10,6 +10,7 @@ import co.electriccoin.zcash.spackle.ClipboardManagerUtil
|
||||||
import co.electriccoin.zcash.ui.MainActivity
|
import co.electriccoin.zcash.ui.MainActivity
|
||||||
import co.electriccoin.zcash.ui.R
|
import co.electriccoin.zcash.ui.R
|
||||||
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
||||||
|
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
|
||||||
import co.electriccoin.zcash.ui.screen.address.view.WalletAddresses
|
import co.electriccoin.zcash.ui.screen.address.view.WalletAddresses
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -27,7 +28,8 @@ private fun WrapWalletAddresses(
|
||||||
val walletAddresses = walletViewModel.addresses.collectAsStateWithLifecycle().value
|
val walletAddresses = walletViewModel.addresses.collectAsStateWithLifecycle().value
|
||||||
|
|
||||||
if (null == walletAddresses) {
|
if (null == walletAddresses) {
|
||||||
// Display loading indicator
|
// Improve this by allowing screen composition and updating it after the data is available
|
||||||
|
CircularScreenProgressIndicator()
|
||||||
} else {
|
} else {
|
||||||
WalletAddresses(
|
WalletAddresses(
|
||||||
walletAddresses,
|
walletAddresses,
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
@file:Suppress("ktlint:standard:filename")
|
||||||
|
|
||||||
|
package co.electriccoin.zcash.ui.screen.balances
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import co.electriccoin.zcash.ui.screen.balances.view.Balances
|
||||||
|
|
||||||
|
@Suppress("UNUSED_PARAMETER")
|
||||||
|
@Composable
|
||||||
|
internal fun WrapBalances(
|
||||||
|
activity: ComponentActivity,
|
||||||
|
goSettings: () -> Unit,
|
||||||
|
) {
|
||||||
|
Balances(
|
||||||
|
onSettings = goSettings,
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
package co.electriccoin.zcash.ui.screen.balances.view
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import co.electriccoin.zcash.ui.R
|
||||||
|
import co.electriccoin.zcash.ui.common.test.CommonTag
|
||||||
|
import co.electriccoin.zcash.ui.design.component.Body
|
||||||
|
import co.electriccoin.zcash.ui.design.component.GradientSurface
|
||||||
|
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
|
||||||
|
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||||
|
|
||||||
|
@Preview("Balances")
|
||||||
|
@Composable
|
||||||
|
private fun ComposablePreview() {
|
||||||
|
ZcashTheme(forceDarkMode = false) {
|
||||||
|
GradientSurface {
|
||||||
|
Balances(
|
||||||
|
onSettings = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO [#1127]: Implement Balances screen
|
||||||
|
// TODO [#1127]: https://github.com/Electric-Coin-Company/zashi-android/issues/1127
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Balances(onSettings: () -> Unit) {
|
||||||
|
Scaffold(topBar = {
|
||||||
|
BalancesTopAppBar(onSettings = onSettings)
|
||||||
|
}) { paddingValues ->
|
||||||
|
BalancesMainContent(
|
||||||
|
modifier =
|
||||||
|
Modifier.padding(
|
||||||
|
top = paddingValues.calculateTopPadding() + ZcashTheme.dimens.spacingDefault,
|
||||||
|
bottom = paddingValues.calculateBottomPadding() + ZcashTheme.dimens.spacingHuge,
|
||||||
|
start = ZcashTheme.dimens.screenHorizontalSpacing,
|
||||||
|
end = ZcashTheme.dimens.screenHorizontalSpacing
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun BalancesTopAppBar(onSettings: () -> Unit) {
|
||||||
|
SmallTopAppBar(
|
||||||
|
showTitleLogo = true,
|
||||||
|
hamburgerMenuActions = {
|
||||||
|
IconButton(
|
||||||
|
onClick = onSettings,
|
||||||
|
modifier = Modifier.testTag(CommonTag.SETTINGS_TOP_BAR_BUTTON)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = co.electriccoin.zcash.ui.design.R.drawable.hamburger_menu_icon),
|
||||||
|
contentDescription = stringResource(id = R.string.settings_menu_content_description)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun BalancesMainContent(modifier: Modifier = Modifier) {
|
||||||
|
Box(
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.then(modifier),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||||
|
|
||||||
|
Body(stringResource(id = R.string.not_implemented_yet))
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ import co.electriccoin.zcash.ui.MainActivity
|
||||||
import co.electriccoin.zcash.ui.R
|
import co.electriccoin.zcash.ui.R
|
||||||
import co.electriccoin.zcash.ui.common.model.VersionInfo
|
import co.electriccoin.zcash.ui.common.model.VersionInfo
|
||||||
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
||||||
|
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
|
||||||
import co.electriccoin.zcash.ui.screen.exportdata.util.FileShareUtil
|
import co.electriccoin.zcash.ui.screen.exportdata.util.FileShareUtil
|
||||||
import co.electriccoin.zcash.ui.screen.exportdata.view.ExportPrivateData
|
import co.electriccoin.zcash.ui.screen.exportdata.view.ExportPrivateData
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
@ -45,7 +46,8 @@ internal fun WrapExportPrivateData(
|
||||||
val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value
|
val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value
|
||||||
|
|
||||||
if (synchronizer == null) {
|
if (synchronizer == null) {
|
||||||
// Display loading indicator
|
// Improve this by allowing screen composition and updating it after the data is available
|
||||||
|
CircularScreenProgressIndicator()
|
||||||
} else {
|
} else {
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
|
@ -3,79 +3,162 @@
|
||||||
package co.electriccoin.zcash.ui.screen.home
|
package co.electriccoin.zcash.ui.screen.home
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import co.electriccoin.zcash.ui.MainActivity
|
import co.electriccoin.zcash.ui.MainActivity
|
||||||
import co.electriccoin.zcash.ui.common.viewmodel.CheckUpdateViewModel
|
import co.electriccoin.zcash.ui.R
|
||||||
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
import co.electriccoin.zcash.ui.common.viewmodel.HomeViewModel
|
||||||
import co.electriccoin.zcash.ui.configuration.ConfigurationEntries
|
import co.electriccoin.zcash.ui.screen.account.WrapAccount
|
||||||
import co.electriccoin.zcash.ui.configuration.RemoteConfig
|
import co.electriccoin.zcash.ui.screen.balances.WrapBalances
|
||||||
|
import co.electriccoin.zcash.ui.screen.home.model.TabItem
|
||||||
import co.electriccoin.zcash.ui.screen.home.view.Home
|
import co.electriccoin.zcash.ui.screen.home.view.Home
|
||||||
import co.electriccoin.zcash.ui.screen.settings.viewmodel.SettingsViewModel
|
import co.electriccoin.zcash.ui.screen.receive.WrapReceive
|
||||||
import co.electriccoin.zcash.ui.screen.update.AppUpdateCheckerImp
|
import co.electriccoin.zcash.ui.screen.send.WrapSend
|
||||||
import co.electriccoin.zcash.ui.screen.update.model.UpdateState
|
import co.electriccoin.zcash.ui.screen.send.model.SendArgumentsWrapper
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
|
||||||
@Composable
|
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
|
@Composable
|
||||||
internal fun MainActivity.WrapHome(
|
internal fun MainActivity.WrapHome(
|
||||||
|
onPageChange: (HomeScreenIndex) -> Unit,
|
||||||
|
goAddressDetails: () -> Unit,
|
||||||
|
goBack: () -> Unit,
|
||||||
|
goHistory: () -> Unit,
|
||||||
goSettings: () -> Unit,
|
goSettings: () -> Unit,
|
||||||
goReceive: () -> Unit,
|
goScan: () -> Unit,
|
||||||
goSend: () -> Unit,
|
sendArgumentsWrapper: SendArgumentsWrapper
|
||||||
goHistory: () -> Unit
|
|
||||||
) {
|
) {
|
||||||
WrapHome(
|
WrapHome(
|
||||||
this,
|
this,
|
||||||
goSettings = goSettings,
|
onPageChange = onPageChange,
|
||||||
goReceive = goReceive,
|
goAddressDetails = goAddressDetails,
|
||||||
goSend = goSend,
|
goBack = goBack,
|
||||||
goHistory = goHistory,
|
goHistory = goHistory,
|
||||||
|
goScan = goScan,
|
||||||
|
goSettings = goSettings,
|
||||||
|
sendArgumentsWrapper = sendArgumentsWrapper
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("LongParameterList", "LongMethod")
|
||||||
@Composable
|
@Composable
|
||||||
@Suppress("LongParameterList")
|
|
||||||
internal fun WrapHome(
|
internal fun WrapHome(
|
||||||
activity: ComponentActivity,
|
activity: ComponentActivity,
|
||||||
goSettings: () -> Unit,
|
goAddressDetails: () -> Unit,
|
||||||
goReceive: () -> Unit,
|
goBack: () -> Unit,
|
||||||
goSend: () -> Unit,
|
|
||||||
goHistory: () -> Unit,
|
goHistory: () -> Unit,
|
||||||
|
goSettings: () -> Unit,
|
||||||
|
goScan: () -> Unit,
|
||||||
|
onPageChange: (HomeScreenIndex) -> Unit,
|
||||||
|
sendArgumentsWrapper: SendArgumentsWrapper
|
||||||
) {
|
) {
|
||||||
// we want to show information about app update, if available
|
val homeViewModel by activity.viewModels<HomeViewModel>()
|
||||||
val checkUpdateViewModel by activity.viewModels<CheckUpdateViewModel> {
|
|
||||||
CheckUpdateViewModel.CheckUpdateViewModelFactory(
|
// Flow for propagating the new page index to the pager in the view layer
|
||||||
activity.application,
|
val forceHomePageIndexFlow: MutableSharedFlow<ForcePage?> =
|
||||||
AppUpdateCheckerImp.new()
|
MutableSharedFlow(
|
||||||
|
Int.MAX_VALUE,
|
||||||
|
Int.MAX_VALUE,
|
||||||
|
BufferOverflow.SUSPEND
|
||||||
)
|
)
|
||||||
}
|
val forceIndex = forceHomePageIndexFlow.collectAsState(initial = null).value
|
||||||
val updateAvailable =
|
|
||||||
checkUpdateViewModel.updateInfo.collectAsStateWithLifecycle().value.let {
|
val homeGoBack: () -> Unit = {
|
||||||
it?.appUpdateInfo != null && it.state == UpdateState.Prepared
|
when (homeViewModel.screenIndex.value) {
|
||||||
|
HomeScreenIndex.ACCOUNT -> goBack()
|
||||||
|
HomeScreenIndex.SEND,
|
||||||
|
HomeScreenIndex.RECEIVE,
|
||||||
|
HomeScreenIndex.BALANCES -> forceHomePageIndexFlow.tryEmit(ForcePage())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val walletViewModel by activity.viewModels<WalletViewModel>()
|
BackHandler {
|
||||||
val walletSnapshot = walletViewModel.walletSnapshot.collectAsStateWithLifecycle().value
|
homeGoBack()
|
||||||
|
}
|
||||||
|
|
||||||
val settingsViewModel by activity.viewModels<SettingsViewModel>()
|
val tabs =
|
||||||
|
persistentListOf(
|
||||||
val isKeepScreenOnWhileSyncing = settingsViewModel.isKeepScreenOnWhileSyncing.collectAsStateWithLifecycle().value
|
TabItem(
|
||||||
val isFiatConversionEnabled = ConfigurationEntries.IS_FIAT_CONVERSION_ENABLED.getValue(RemoteConfig.current)
|
index = HomeScreenIndex.ACCOUNT,
|
||||||
|
title = stringResource(id = R.string.home_tab_account),
|
||||||
if (null == walletSnapshot) {
|
testTag = HomeTag.TAB_ACCOUNT,
|
||||||
// Display loading indicator
|
screenContent = {
|
||||||
} else {
|
WrapAccount(
|
||||||
Home(
|
activity = activity,
|
||||||
walletSnapshot = walletSnapshot,
|
goHistory = goHistory,
|
||||||
isUpdateAvailable = updateAvailable,
|
goSettings = goSettings,
|
||||||
isKeepScreenOnDuringSync = isKeepScreenOnWhileSyncing,
|
)
|
||||||
isFiatConversionEnabled = isFiatConversionEnabled,
|
}
|
||||||
goSettings = goSettings,
|
),
|
||||||
goReceive = goReceive,
|
TabItem(
|
||||||
goSend = goSend,
|
index = HomeScreenIndex.SEND,
|
||||||
goHistory = goHistory
|
title = stringResource(id = R.string.home_tab_send),
|
||||||
|
testTag = HomeTag.TAB_SEND,
|
||||||
|
screenContent = {
|
||||||
|
WrapSend(
|
||||||
|
activity = activity,
|
||||||
|
goToQrScanner = goScan,
|
||||||
|
goBack = homeGoBack,
|
||||||
|
goSettings = goSettings,
|
||||||
|
sendArgumentsWrapper = sendArgumentsWrapper
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
TabItem(
|
||||||
|
index = HomeScreenIndex.RECEIVE,
|
||||||
|
title = stringResource(id = R.string.home_tab_receive),
|
||||||
|
testTag = HomeTag.TAB_RECEIVE,
|
||||||
|
screenContent = {
|
||||||
|
WrapReceive(
|
||||||
|
activity = activity,
|
||||||
|
onSettings = goSettings,
|
||||||
|
onAddressDetails = goAddressDetails,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
),
|
||||||
|
TabItem(
|
||||||
|
index = HomeScreenIndex.BALANCES,
|
||||||
|
title = stringResource(id = R.string.home_tab_balances),
|
||||||
|
testTag = HomeTag.TAB_BALANCES,
|
||||||
|
screenContent = {
|
||||||
|
WrapBalances(
|
||||||
|
activity = activity,
|
||||||
|
goSettings = goSettings
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
activity.reportFullyDrawn()
|
Home(
|
||||||
|
subScreens = tabs,
|
||||||
|
forcePage = forceIndex,
|
||||||
|
onPageChange = onPageChange
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper class used to pass forced pages index into the view layer
|
||||||
|
*/
|
||||||
|
class ForcePage(
|
||||||
|
val currentPage: HomeScreenIndex = HomeScreenIndex.ACCOUNT,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enum of the Home screen sub-screens
|
||||||
|
*/
|
||||||
|
enum class HomeScreenIndex {
|
||||||
|
// WARN: Be careful when re-ordering these, as the ordinal number states for their order
|
||||||
|
ACCOUNT,
|
||||||
|
SEND,
|
||||||
|
RECEIVE,
|
||||||
|
BALANCES, ;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromIndex(index: Int) = entries[index]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,8 @@ package co.electriccoin.zcash.ui.screen.home
|
||||||
* These are only used for automated testing.
|
* These are only used for automated testing.
|
||||||
*/
|
*/
|
||||||
object HomeTag {
|
object HomeTag {
|
||||||
const val STATUS_VIEWS = "status_views"
|
const val TAB_ACCOUNT = "tab_account"
|
||||||
const val SINGLE_LINE_TEXT = "single_line_text"
|
const val TAB_SEND = "tab_send"
|
||||||
const val FIAT_CONVERSION = "fiat_conversion"
|
const val TAB_RECEIVE = "tab_receive"
|
||||||
const val SETTINGS_TOP_BAR_BUTTON = "settings_top_bar_button"
|
const val TAB_BALANCES = "tab_balances"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package co.electriccoin.zcash.ui.screen.home.model
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import co.electriccoin.zcash.ui.screen.home.HomeScreenIndex
|
||||||
|
|
||||||
|
data class TabItem(
|
||||||
|
val index: HomeScreenIndex,
|
||||||
|
val title: String,
|
||||||
|
val testTag: String,
|
||||||
|
val screenContent: @Composable () -> Unit
|
||||||
|
)
|
|
@ -1,117 +0,0 @@
|
||||||
package co.electriccoin.zcash.ui.screen.home.model
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.compose.ui.text.intl.Locale
|
|
||||||
import cash.z.ecc.android.sdk.Synchronizer
|
|
||||||
import cash.z.ecc.android.sdk.model.FiatCurrencyConversionRateState
|
|
||||||
import cash.z.ecc.android.sdk.model.MonetarySeparators
|
|
||||||
import cash.z.ecc.android.sdk.model.PercentDecimal
|
|
||||||
import cash.z.ecc.android.sdk.model.toFiatCurrencyState
|
|
||||||
import cash.z.ecc.android.sdk.model.toZecString
|
|
||||||
import cash.z.ecc.sdk.extension.toPercentageWithDecimal
|
|
||||||
import co.electriccoin.zcash.ui.R
|
|
||||||
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
|
|
||||||
import co.electriccoin.zcash.ui.common.model.spendableBalance
|
|
||||||
import co.electriccoin.zcash.ui.common.model.totalBalance
|
|
||||||
import co.electriccoin.zcash.ui.common.toKotlinLocale
|
|
||||||
|
|
||||||
data class WalletDisplayValues(
|
|
||||||
val progress: PercentDecimal,
|
|
||||||
val zecAmountText: String,
|
|
||||||
val statusText: String,
|
|
||||||
val fiatCurrencyAmountState: FiatCurrencyConversionRateState,
|
|
||||||
val fiatCurrencyAmountText: String
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
@Suppress("MagicNumber", "LongMethod")
|
|
||||||
internal fun getNextValues(
|
|
||||||
context: Context,
|
|
||||||
walletSnapshot: WalletSnapshot,
|
|
||||||
updateAvailable: Boolean
|
|
||||||
): WalletDisplayValues {
|
|
||||||
var progress = PercentDecimal.ZERO_PERCENT
|
|
||||||
val zecAmountText = walletSnapshot.totalBalance().toZecString()
|
|
||||||
var statusText = ""
|
|
||||||
// TODO [#578]: Provide Zatoshi -> USD fiat currency formatting
|
|
||||||
// TODO [#578]: https://github.com/Electric-Coin-Company/zcash-android-wallet-sdk/issues/578
|
|
||||||
// We'll ideally provide a "fresh" currencyConversion object here
|
|
||||||
val fiatCurrencyAmountState =
|
|
||||||
walletSnapshot.spendableBalance().toFiatCurrencyState(
|
|
||||||
null,
|
|
||||||
Locale.current.toKotlinLocale(),
|
|
||||||
MonetarySeparators.current()
|
|
||||||
)
|
|
||||||
var fiatCurrencyAmountText = getFiatCurrencyRateValue(context, fiatCurrencyAmountState)
|
|
||||||
|
|
||||||
when (walletSnapshot.status) {
|
|
||||||
Synchronizer.Status.SYNCING -> {
|
|
||||||
progress = walletSnapshot.progress
|
|
||||||
// we add "so far" to the amount
|
|
||||||
if (fiatCurrencyAmountState != FiatCurrencyConversionRateState.Unavailable) {
|
|
||||||
fiatCurrencyAmountText =
|
|
||||||
context.getString(
|
|
||||||
R.string.home_status_syncing_amount_suffix,
|
|
||||||
fiatCurrencyAmountText
|
|
||||||
)
|
|
||||||
}
|
|
||||||
statusText =
|
|
||||||
context.getString(
|
|
||||||
R.string.home_status_syncing_format,
|
|
||||||
walletSnapshot.progress.toPercentageWithDecimal()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Synchronizer.Status.SYNCED -> {
|
|
||||||
statusText =
|
|
||||||
if (updateAvailable) {
|
|
||||||
context.getString(R.string.home_status_update)
|
|
||||||
} else {
|
|
||||||
context.getString(R.string.home_status_up_to_date)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Synchronizer.Status.DISCONNECTED -> {
|
|
||||||
statusText =
|
|
||||||
context.getString(
|
|
||||||
R.string.home_status_error,
|
|
||||||
context.getString(R.string.home_status_error_connection)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Synchronizer.Status.STOPPED -> {
|
|
||||||
statusText = context.getString(R.string.home_status_stopped)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// more detailed error message
|
|
||||||
walletSnapshot.synchronizerError?.let {
|
|
||||||
statusText =
|
|
||||||
context.getString(
|
|
||||||
R.string.home_status_error,
|
|
||||||
walletSnapshot.synchronizerError.getCauseMessage()
|
|
||||||
?: context.getString(R.string.home_status_error_unknown)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return WalletDisplayValues(
|
|
||||||
progress = progress,
|
|
||||||
zecAmountText = zecAmountText,
|
|
||||||
statusText = statusText,
|
|
||||||
fiatCurrencyAmountState = fiatCurrencyAmountState,
|
|
||||||
fiatCurrencyAmountText = fiatCurrencyAmountText
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getFiatCurrencyRateValue(
|
|
||||||
context: Context,
|
|
||||||
fiatCurrencyAmountState: FiatCurrencyConversionRateState
|
|
||||||
): String {
|
|
||||||
return fiatCurrencyAmountState.let { state ->
|
|
||||||
when (state) {
|
|
||||||
is FiatCurrencyConversionRateState.Current -> state.formattedFiatValue
|
|
||||||
is FiatCurrencyConversionRateState.Stale -> state.formattedFiatValue
|
|
||||||
is FiatCurrencyConversionRateState.Unavailable -> {
|
|
||||||
context.getString(R.string.fiat_currency_conversion_rate_unavailable)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,43 +1,40 @@
|
||||||
@file:Suppress("TooManyFunctions")
|
|
||||||
|
|
||||||
package co.electriccoin.zcash.ui.screen.home.view
|
package co.electriccoin.zcash.ui.screen.home.view
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.gestures.Orientation
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
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.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.pager.PageSize
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.foundation.pager.PagerDefaults
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
|
import androidx.compose.material3.Divider
|
||||||
|
import androidx.compose.material3.DividerDefaults
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Tab
|
||||||
|
import androidx.compose.material3.TabRow
|
||||||
|
import androidx.compose.material3.TabRowDefaults
|
||||||
|
import androidx.compose.material3.TabRowDefaults.tabIndicatorOffset
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.platform.testTag
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import cash.z.ecc.android.sdk.Synchronizer
|
import androidx.compose.ui.unit.dp
|
||||||
import cash.z.ecc.android.sdk.model.FiatCurrencyConversionRateState
|
import co.electriccoin.zcash.spackle.Twig
|
||||||
import co.electriccoin.zcash.ui.R
|
|
||||||
import co.electriccoin.zcash.ui.common.DisableScreenTimeout
|
|
||||||
import co.electriccoin.zcash.ui.common.model.WalletSnapshot
|
|
||||||
import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT
|
|
||||||
import co.electriccoin.zcash.ui.design.component.Body
|
|
||||||
import co.electriccoin.zcash.ui.design.component.BodyWithFiatCurrencySymbol
|
|
||||||
import co.electriccoin.zcash.ui.design.component.GradientSurface
|
import co.electriccoin.zcash.ui.design.component.GradientSurface
|
||||||
import co.electriccoin.zcash.ui.design.component.HeaderWithZecIcon
|
import co.electriccoin.zcash.ui.design.component.NavigationTabText
|
||||||
import co.electriccoin.zcash.ui.design.component.PrimaryButton
|
|
||||||
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
|
|
||||||
import co.electriccoin.zcash.ui.design.component.TertiaryButton
|
|
||||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||||
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
|
import co.electriccoin.zcash.ui.screen.home.ForcePage
|
||||||
import co.electriccoin.zcash.ui.screen.home.HomeTag
|
import co.electriccoin.zcash.ui.screen.home.HomeScreenIndex
|
||||||
import co.electriccoin.zcash.ui.screen.home.model.WalletDisplayValues
|
import co.electriccoin.zcash.ui.screen.home.model.TabItem
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Preview("Home")
|
@Preview("Home")
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -45,184 +42,115 @@ private fun ComposablePreview() {
|
||||||
ZcashTheme(forceDarkMode = false) {
|
ZcashTheme(forceDarkMode = false) {
|
||||||
GradientSurface {
|
GradientSurface {
|
||||||
Home(
|
Home(
|
||||||
walletSnapshot = WalletSnapshotFixture.new(),
|
subScreens = persistentListOf(),
|
||||||
isUpdateAvailable = false,
|
forcePage = null,
|
||||||
isKeepScreenOnDuringSync = false,
|
onPageChange = {}
|
||||||
isFiatConversionEnabled = false,
|
|
||||||
goSettings = {},
|
|
||||||
goReceive = {},
|
|
||||||
goSend = {},
|
|
||||||
goHistory = {}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
@Suppress("LongMethod")
|
||||||
@Composable
|
@Composable
|
||||||
fun Home(
|
fun Home(
|
||||||
walletSnapshot: WalletSnapshot,
|
subScreens: ImmutableList<TabItem>,
|
||||||
isUpdateAvailable: Boolean,
|
forcePage: ForcePage?,
|
||||||
isKeepScreenOnDuringSync: Boolean?,
|
onPageChange: (HomeScreenIndex) -> Unit,
|
||||||
isFiatConversionEnabled: Boolean,
|
|
||||||
goSettings: () -> Unit,
|
|
||||||
goReceive: () -> Unit,
|
|
||||||
goSend: () -> Unit,
|
|
||||||
goHistory: () -> Unit
|
|
||||||
) {
|
) {
|
||||||
Scaffold(topBar = {
|
val pagerState =
|
||||||
HomeTopAppBar(onSettings = goSettings)
|
rememberPagerState(
|
||||||
}) { paddingValues ->
|
initialPage = 0,
|
||||||
HomeMainContent(
|
initialPageOffsetFraction = 0f,
|
||||||
walletSnapshot = walletSnapshot,
|
pageCount = { subScreens.size }
|
||||||
isUpdateAvailable = isUpdateAvailable,
|
|
||||||
isKeepScreenOnDuringSync = isKeepScreenOnDuringSync,
|
|
||||||
isFiatConversionEnabled = isFiatConversionEnabled,
|
|
||||||
goReceive = goReceive,
|
|
||||||
goSend = goSend,
|
|
||||||
goHistory = goHistory,
|
|
||||||
modifier =
|
|
||||||
Modifier.padding(
|
|
||||||
top = paddingValues.calculateTopPadding() + ZcashTheme.dimens.spacingDefault,
|
|
||||||
bottom = paddingValues.calculateBottomPadding() + ZcashTheme.dimens.spacingHuge,
|
|
||||||
start = ZcashTheme.dimens.screenHorizontalSpacing,
|
|
||||||
end = ZcashTheme.dimens.screenHorizontalSpacing
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
// Listening for the current page change
|
||||||
private fun HomeTopAppBar(onSettings: () -> Unit) {
|
LaunchedEffect(pagerState) {
|
||||||
SmallTopAppBar(
|
snapshotFlow {
|
||||||
showTitleLogo = true,
|
pagerState.currentPage
|
||||||
hamburgerMenuActions = {
|
}.distinctUntilChanged()
|
||||||
IconButton(
|
.collect { page ->
|
||||||
onClick = onSettings,
|
Twig.info { "Current pager page: $page" }
|
||||||
modifier = Modifier.testTag(HomeTag.SETTINGS_TOP_BAR_BUTTON)
|
onPageChange(HomeScreenIndex.fromIndex(page))
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = co.electriccoin.zcash.ui.design.R.drawable.hamburger_menu_icon),
|
|
||||||
contentDescription = stringResource(id = R.string.home_menu_content_description)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
// Force page change e.g. when system back navigation event detected
|
||||||
@Composable
|
forcePage?.let {
|
||||||
private fun HomeMainContent(
|
LaunchedEffect(forcePage) {
|
||||||
walletSnapshot: WalletSnapshot,
|
pagerState.animateScrollToPage(forcePage.currentPage.ordinal)
|
||||||
isUpdateAvailable: Boolean,
|
|
||||||
isKeepScreenOnDuringSync: Boolean?,
|
|
||||||
isFiatConversionEnabled: Boolean,
|
|
||||||
goReceive: () -> Unit,
|
|
||||||
goSend: () -> Unit,
|
|
||||||
goHistory: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
Modifier
|
|
||||||
.fillMaxHeight()
|
|
||||||
.verticalScroll(
|
|
||||||
rememberScrollState()
|
|
||||||
)
|
|
||||||
.then(modifier),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
|
||||||
|
|
||||||
Status(walletSnapshot, isUpdateAvailable, isFiatConversionEnabled)
|
|
||||||
|
|
||||||
Spacer(
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.fillMaxHeight()
|
|
||||||
.weight(MINIMAL_WEIGHT)
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
|
||||||
|
|
||||||
PrimaryButton(
|
|
||||||
onClick = goSend,
|
|
||||||
text = stringResource(R.string.home_button_send)
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
|
||||||
|
|
||||||
PrimaryButton(
|
|
||||||
onClick = goReceive,
|
|
||||||
text = stringResource(R.string.home_button_receive)
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
|
||||||
|
|
||||||
TertiaryButton(onClick = goHistory, text = stringResource(R.string.home_button_history))
|
|
||||||
|
|
||||||
if (isKeepScreenOnDuringSync == true && walletSnapshot.status == Synchronizer.Status.SYNCING) {
|
|
||||||
DisableScreenTimeout()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
val coroutineScope = rememberCoroutineScope()
|
||||||
@Suppress("LongMethod", "MagicNumber")
|
|
||||||
private fun Status(
|
|
||||||
walletSnapshot: WalletSnapshot,
|
|
||||||
updateAvailable: Boolean,
|
|
||||||
isFiatConversionEnabled: Boolean
|
|
||||||
) {
|
|
||||||
val walletDisplayValues =
|
|
||||||
WalletDisplayValues.getNextValues(
|
|
||||||
LocalContext.current,
|
|
||||||
walletSnapshot,
|
|
||||||
updateAvailable
|
|
||||||
)
|
|
||||||
|
|
||||||
Column(
|
Scaffold(
|
||||||
modifier =
|
bottomBar = {
|
||||||
Modifier
|
Column {
|
||||||
.fillMaxWidth()
|
Divider(
|
||||||
.testTag(HomeTag.STATUS_VIEWS),
|
thickness = DividerDefaults.Thickness,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
color = ZcashTheme.colors.dividerColor
|
||||||
) {
|
)
|
||||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
|
TabRow(
|
||||||
|
selectedTabIndex = pagerState.currentPage,
|
||||||
if (walletDisplayValues.zecAmountText.isNotEmpty()) {
|
// Don't use the predefined divider, as it's fixed position is below the tabs bar
|
||||||
HeaderWithZecIcon(amount = walletDisplayValues.zecAmountText)
|
divider = {},
|
||||||
}
|
indicator = { tabPositions ->
|
||||||
|
TabRowDefaults.Indicator(
|
||||||
if (isFiatConversionEnabled) {
|
modifier =
|
||||||
Column(Modifier.testTag(HomeTag.FIAT_CONVERSION)) {
|
Modifier
|
||||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
.tabIndicatorOffset(tabPositions[pagerState.currentPage])
|
||||||
|
.padding(horizontal = ZcashTheme.dimens.spacingDefault),
|
||||||
when (walletDisplayValues.fiatCurrencyAmountState) {
|
color = ZcashTheme.colors.complementaryColor
|
||||||
is FiatCurrencyConversionRateState.Current -> {
|
|
||||||
BodyWithFiatCurrencySymbol(
|
|
||||||
amount = walletDisplayValues.fiatCurrencyAmountText
|
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
is FiatCurrencyConversionRateState.Stale -> {
|
modifier =
|
||||||
// Note: we should show information about staleness too
|
Modifier
|
||||||
BodyWithFiatCurrencySymbol(
|
.navigationBarsPadding()
|
||||||
amount = walletDisplayValues.fiatCurrencyAmountText
|
.padding(all = ZcashTheme.dimens.spacingDefault)
|
||||||
|
) {
|
||||||
|
subScreens.forEachIndexed { index, item ->
|
||||||
|
val selected = index == pagerState.currentPage
|
||||||
|
Tab(
|
||||||
|
selected = selected,
|
||||||
|
text = {
|
||||||
|
NavigationTabText(
|
||||||
|
text = item.title,
|
||||||
|
selected = selected
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(all = 0.dp)
|
||||||
|
.testTag(item.testTag),
|
||||||
|
onClick = { coroutineScope.launch { pagerState.animateScrollToPage(index) } },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is FiatCurrencyConversionRateState.Unavailable -> {
|
|
||||||
Body(text = walletDisplayValues.fiatCurrencyAmountText)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
) { paddingValues ->
|
||||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
|
HorizontalPager(
|
||||||
|
state = pagerState,
|
||||||
if (walletDisplayValues.statusText.isNotEmpty()) {
|
pageSpacing = 0.dp,
|
||||||
Body(
|
pageSize = PageSize.Fill,
|
||||||
text = walletDisplayValues.statusText,
|
pageNestedScrollConnection =
|
||||||
modifier = Modifier.testTag(HomeTag.SINGLE_LINE_TEXT)
|
PagerDefaults.pageNestedScrollConnection(
|
||||||
)
|
Orientation.Horizontal
|
||||||
}
|
),
|
||||||
|
pageContent = { index ->
|
||||||
|
subScreens[index].screenContent()
|
||||||
|
},
|
||||||
|
key = { index ->
|
||||||
|
subScreens[index].title
|
||||||
|
},
|
||||||
|
beyondBoundsPageCount = 1,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(
|
||||||
|
bottom = paddingValues.calculateBottomPadding()
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,28 +7,14 @@ import androidx.activity.viewModels
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import cash.z.ecc.android.sdk.model.WalletAddresses
|
import cash.z.ecc.android.sdk.model.WalletAddresses
|
||||||
import co.electriccoin.zcash.ui.MainActivity
|
|
||||||
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
||||||
|
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
|
||||||
import co.electriccoin.zcash.ui.screen.receive.view.Receive
|
import co.electriccoin.zcash.ui.screen.receive.view.Receive
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Suppress("LongParameterList")
|
|
||||||
internal fun MainActivity.WrapReceive(
|
|
||||||
onBack: () -> Unit,
|
|
||||||
onAddressDetails: () -> Unit,
|
|
||||||
) {
|
|
||||||
WrapReceive(
|
|
||||||
this,
|
|
||||||
onBack = onBack,
|
|
||||||
onAddressDetails = onAddressDetails,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
@Suppress("LongParameterList")
|
|
||||||
internal fun WrapReceive(
|
internal fun WrapReceive(
|
||||||
activity: ComponentActivity,
|
activity: ComponentActivity,
|
||||||
onBack: () -> Unit,
|
onSettings: () -> Unit,
|
||||||
onAddressDetails: () -> Unit,
|
onAddressDetails: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val viewModel by activity.viewModels<WalletViewModel>()
|
val viewModel by activity.viewModels<WalletViewModel>()
|
||||||
|
@ -36,24 +22,24 @@ internal fun WrapReceive(
|
||||||
|
|
||||||
WrapReceive(
|
WrapReceive(
|
||||||
walletAddresses,
|
walletAddresses,
|
||||||
onBack = onBack,
|
onSettings = onSettings,
|
||||||
onAddressDetails = onAddressDetails,
|
onAddressDetails = onAddressDetails,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Suppress("LongParameterList")
|
|
||||||
internal fun WrapReceive(
|
internal fun WrapReceive(
|
||||||
walletAddresses: WalletAddresses?,
|
walletAddresses: WalletAddresses?,
|
||||||
onBack: () -> Unit,
|
onSettings: () -> Unit,
|
||||||
onAddressDetails: () -> Unit,
|
onAddressDetails: () -> Unit,
|
||||||
) {
|
) {
|
||||||
if (null == walletAddresses) {
|
if (null == walletAddresses) {
|
||||||
// Display loading indicator
|
// Improve this by allowing screen composition and updating it after the data is available
|
||||||
|
CircularScreenProgressIndicator()
|
||||||
} else {
|
} else {
|
||||||
Receive(
|
Receive(
|
||||||
walletAddresses.unified,
|
walletAddresses.unified,
|
||||||
onBack = onBack,
|
onSettings = onSettings,
|
||||||
onAddressDetails = onAddressDetails,
|
onAddressDetails = onAddressDetails,
|
||||||
onAdjustBrightness = { /* Just for testing */ }
|
onAdjustBrightness = { /* Just for testing */ }
|
||||||
)
|
)
|
||||||
|
|
|
@ -22,6 +22,8 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
@ -32,6 +34,7 @@ import cash.z.ecc.android.sdk.model.WalletAddress
|
||||||
import co.electriccoin.zcash.ui.R
|
import co.electriccoin.zcash.ui.R
|
||||||
import co.electriccoin.zcash.ui.common.BrightenScreen
|
import co.electriccoin.zcash.ui.common.BrightenScreen
|
||||||
import co.electriccoin.zcash.ui.common.DisableScreenTimeout
|
import co.electriccoin.zcash.ui.common.DisableScreenTimeout
|
||||||
|
import co.electriccoin.zcash.ui.common.test.CommonTag
|
||||||
import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT
|
import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT
|
||||||
import co.electriccoin.zcash.ui.design.component.Body
|
import co.electriccoin.zcash.ui.design.component.Body
|
||||||
import co.electriccoin.zcash.ui.design.component.GradientSurface
|
import co.electriccoin.zcash.ui.design.component.GradientSurface
|
||||||
|
@ -50,7 +53,7 @@ private fun ComposablePreview() {
|
||||||
GradientSurface {
|
GradientSurface {
|
||||||
Receive(
|
Receive(
|
||||||
walletAddress = runBlocking { WalletAddressFixture.unified() },
|
walletAddress = runBlocking { WalletAddressFixture.unified() },
|
||||||
onBack = {},
|
onSettings = {},
|
||||||
onAddressDetails = {},
|
onAddressDetails = {},
|
||||||
onAdjustBrightness = {},
|
onAdjustBrightness = {},
|
||||||
)
|
)
|
||||||
|
@ -61,7 +64,7 @@ private fun ComposablePreview() {
|
||||||
@Composable
|
@Composable
|
||||||
fun Receive(
|
fun Receive(
|
||||||
walletAddress: WalletAddress,
|
walletAddress: WalletAddress,
|
||||||
onBack: () -> Unit,
|
onSettings: () -> Unit,
|
||||||
onAddressDetails: () -> Unit,
|
onAddressDetails: () -> Unit,
|
||||||
onAdjustBrightness: (Boolean) -> Unit,
|
onAdjustBrightness: (Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
@ -71,7 +74,7 @@ fun Receive(
|
||||||
Column {
|
Column {
|
||||||
ReceiveTopAppBar(
|
ReceiveTopAppBar(
|
||||||
adjustBrightness = brightness,
|
adjustBrightness = brightness,
|
||||||
onBack = onBack,
|
onSettings = onSettings,
|
||||||
onBrightness = {
|
onBrightness = {
|
||||||
onAdjustBrightness(!brightness)
|
onAdjustBrightness(!brightness)
|
||||||
setBrightness(!brightness)
|
setBrightness(!brightness)
|
||||||
|
@ -97,14 +100,11 @@ fun Receive(
|
||||||
@Composable
|
@Composable
|
||||||
private fun ReceiveTopAppBar(
|
private fun ReceiveTopAppBar(
|
||||||
adjustBrightness: Boolean,
|
adjustBrightness: Boolean,
|
||||||
onBack: () -> Unit,
|
onSettings: () -> Unit,
|
||||||
onBrightness: () -> Unit
|
onBrightness: () -> Unit
|
||||||
) {
|
) {
|
||||||
SmallTopAppBar(
|
SmallTopAppBar(
|
||||||
titleText = stringResource(id = R.string.receive_title),
|
titleText = stringResource(id = R.string.receive_title),
|
||||||
backText = stringResource(id = R.string.receive_back),
|
|
||||||
backContentDescriptionText = stringResource(id = R.string.receive_back_content_description),
|
|
||||||
onBack = onBack,
|
|
||||||
regularActions = {
|
regularActions = {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onBrightness
|
onClick = onBrightness
|
||||||
|
@ -119,6 +119,17 @@ private fun ReceiveTopAppBar(
|
||||||
contentDescription = stringResource(R.string.receive_brightness_content_description)
|
contentDescription = stringResource(R.string.receive_brightness_content_description)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
hamburgerMenuActions = {
|
||||||
|
IconButton(
|
||||||
|
onClick = onSettings,
|
||||||
|
modifier = Modifier.testTag(CommonTag.SETTINGS_TOP_BAR_BUTTON)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = co.electriccoin.zcash.ui.design.R.drawable.hamburger_menu_icon),
|
||||||
|
contentDescription = stringResource(id = R.string.settings_menu_content_description)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import cash.z.ecc.sdk.model.ZecRequest
|
||||||
import co.electriccoin.zcash.ui.MainActivity
|
import co.electriccoin.zcash.ui.MainActivity
|
||||||
import co.electriccoin.zcash.ui.R
|
import co.electriccoin.zcash.ui.R
|
||||||
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
||||||
|
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
|
||||||
import co.electriccoin.zcash.ui.screen.request.view.Request
|
import co.electriccoin.zcash.ui.screen.request.view.Request
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
|
@ -29,7 +30,8 @@ private fun WrapRequest(
|
||||||
val walletAddresses = walletViewModel.addresses.collectAsStateWithLifecycle().value
|
val walletAddresses = walletViewModel.addresses.collectAsStateWithLifecycle().value
|
||||||
|
|
||||||
if (null == walletAddresses) {
|
if (null == walletAddresses) {
|
||||||
// Display loading indicator
|
// Improve this by allowing screen composition and updating it after the data is available
|
||||||
|
CircularScreenProgressIndicator()
|
||||||
} else {
|
} else {
|
||||||
Request(
|
Request(
|
||||||
walletAddresses.unified,
|
walletAddresses.unified,
|
||||||
|
|
|
@ -10,6 +10,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import co.electriccoin.zcash.ui.MainActivity
|
import co.electriccoin.zcash.ui.MainActivity
|
||||||
import co.electriccoin.zcash.ui.R
|
import co.electriccoin.zcash.ui.R
|
||||||
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
||||||
|
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
|
||||||
import co.electriccoin.zcash.ui.screen.scan.util.SettingsUtil
|
import co.electriccoin.zcash.ui.screen.scan.util.SettingsUtil
|
||||||
import co.electriccoin.zcash.ui.screen.scan.view.Scan
|
import co.electriccoin.zcash.ui.screen.scan.view.Scan
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -40,7 +41,8 @@ fun WrapScan(
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
if (synchronizer == null) {
|
if (synchronizer == null) {
|
||||||
// Display loading indicator
|
// Improve this by allowing screen composition and updating it after the data is available
|
||||||
|
CircularScreenProgressIndicator()
|
||||||
} else {
|
} else {
|
||||||
Scan(
|
Scan(
|
||||||
snackbarHostState = snackbarHostState,
|
snackbarHostState = snackbarHostState,
|
||||||
|
|
|
@ -9,6 +9,7 @@ import co.electriccoin.zcash.ui.MainActivity
|
||||||
import co.electriccoin.zcash.ui.R
|
import co.electriccoin.zcash.ui.R
|
||||||
import co.electriccoin.zcash.ui.common.viewmodel.SecretState
|
import co.electriccoin.zcash.ui.common.viewmodel.SecretState
|
||||||
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
||||||
|
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
|
||||||
import co.electriccoin.zcash.ui.screen.seedrecovery.view.SeedRecovery
|
import co.electriccoin.zcash.ui.screen.seedrecovery.view.SeedRecovery
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -39,7 +40,8 @@ private fun WrapSeedRecovery(
|
||||||
val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value
|
val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value
|
||||||
|
|
||||||
if (null == synchronizer || null == persistableWallet) {
|
if (null == synchronizer || null == persistableWallet) {
|
||||||
// Display loading indicator
|
// Improve this by allowing screen composition and updating it after the data is available
|
||||||
|
CircularScreenProgressIndicator()
|
||||||
} else {
|
} else {
|
||||||
SeedRecovery(
|
SeedRecovery(
|
||||||
persistableWallet,
|
persistableWallet,
|
||||||
|
|
|
@ -18,9 +18,9 @@ import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
import cash.z.ecc.android.sdk.model.ZecSend
|
import cash.z.ecc.android.sdk.model.ZecSend
|
||||||
import cash.z.ecc.sdk.extension.send
|
import cash.z.ecc.sdk.extension.send
|
||||||
import co.electriccoin.zcash.spackle.Twig
|
import co.electriccoin.zcash.spackle.Twig
|
||||||
import co.electriccoin.zcash.ui.MainActivity
|
|
||||||
import co.electriccoin.zcash.ui.common.model.spendableBalance
|
import co.electriccoin.zcash.ui.common.model.spendableBalance
|
||||||
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
||||||
|
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
|
||||||
import co.electriccoin.zcash.ui.screen.send.ext.Saver
|
import co.electriccoin.zcash.ui.screen.send.ext.Saver
|
||||||
import co.electriccoin.zcash.ui.screen.send.model.SendArgumentsWrapper
|
import co.electriccoin.zcash.ui.screen.send.model.SendArgumentsWrapper
|
||||||
import co.electriccoin.zcash.ui.screen.send.model.SendStage
|
import co.electriccoin.zcash.ui.screen.send.model.SendStage
|
||||||
|
@ -28,20 +28,12 @@ import co.electriccoin.zcash.ui.screen.send.view.Send
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun MainActivity.WrapSend(
|
internal fun WrapSend(
|
||||||
sendArgumentsWrapper: SendArgumentsWrapper?,
|
|
||||||
goToQrScanner: () -> Unit,
|
|
||||||
goBack: () -> Unit
|
|
||||||
) {
|
|
||||||
WrapSend(this, sendArgumentsWrapper, goToQrScanner, goBack)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun WrapSend(
|
|
||||||
activity: ComponentActivity,
|
activity: ComponentActivity,
|
||||||
sendArgumentsWrapper: SendArgumentsWrapper?,
|
sendArgumentsWrapper: SendArgumentsWrapper?,
|
||||||
goToQrScanner: () -> Unit,
|
goToQrScanner: () -> Unit,
|
||||||
goBack: () -> Unit
|
goBack: () -> Unit,
|
||||||
|
goSettings: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val hasCameraFeature = activity.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
|
val hasCameraFeature = activity.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
|
||||||
|
|
||||||
|
@ -53,7 +45,16 @@ private fun WrapSend(
|
||||||
|
|
||||||
val spendingKey = walletViewModel.spendingKey.collectAsStateWithLifecycle().value
|
val spendingKey = walletViewModel.spendingKey.collectAsStateWithLifecycle().value
|
||||||
|
|
||||||
WrapSend(sendArgumentsWrapper, synchronizer, spendableBalance, spendingKey, goToQrScanner, goBack, hasCameraFeature)
|
WrapSend(
|
||||||
|
sendArgumentsWrapper,
|
||||||
|
synchronizer,
|
||||||
|
spendableBalance,
|
||||||
|
spendingKey,
|
||||||
|
goToQrScanner,
|
||||||
|
goBack,
|
||||||
|
goSettings,
|
||||||
|
hasCameraFeature
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
|
@ -66,6 +67,7 @@ internal fun WrapSend(
|
||||||
spendingKey: UnifiedSpendingKey?,
|
spendingKey: UnifiedSpendingKey?,
|
||||||
goToQrScanner: () -> Unit,
|
goToQrScanner: () -> Unit,
|
||||||
goBack: () -> Unit,
|
goBack: () -> Unit,
|
||||||
|
goSettings: () -> Unit,
|
||||||
hasCameraFeature: Boolean
|
hasCameraFeature: Boolean
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
@ -83,11 +85,13 @@ internal fun WrapSend(
|
||||||
when (sendStage) {
|
when (sendStage) {
|
||||||
SendStage.Form -> goBack()
|
SendStage.Form -> goBack()
|
||||||
SendStage.Confirmation -> setSendStage(SendStage.Form)
|
SendStage.Confirmation -> setSendStage(SendStage.Form)
|
||||||
SendStage.Sending -> { // no action - wait until done
|
SendStage.Sending -> { /* no action - wait until the sending is done */ }
|
||||||
}
|
|
||||||
|
|
||||||
SendStage.SendFailure -> setSendStage(SendStage.Form)
|
SendStage.SendFailure -> setSendStage(SendStage.Form)
|
||||||
SendStage.SendSuccessful -> goBack()
|
SendStage.SendSuccessful -> {
|
||||||
|
setZecSend(null)
|
||||||
|
setSendStage(SendStage.Form)
|
||||||
|
goBack()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +100,8 @@ internal fun WrapSend(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (null == synchronizer || null == spendableBalance || null == spendingKey) {
|
if (null == synchronizer || null == spendableBalance || null == spendingKey) {
|
||||||
// Display loading indicator
|
// Improve this by allowing screen composition and updating it after the data is available
|
||||||
|
CircularScreenProgressIndicator()
|
||||||
} else {
|
} else {
|
||||||
Send(
|
Send(
|
||||||
mySpendableBalance = spendableBalance,
|
mySpendableBalance = spendableBalance,
|
||||||
|
@ -106,6 +111,7 @@ internal fun WrapSend(
|
||||||
zecSend = zecSend,
|
zecSend = zecSend,
|
||||||
onZecSendChange = setZecSend,
|
onZecSendChange = setZecSend,
|
||||||
onBack = onBackAction,
|
onBack = onBackAction,
|
||||||
|
onSettings = goSettings,
|
||||||
onCreateAndSend = {
|
onCreateAndSend = {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
Twig.debug { "Sending transaction" }
|
Twig.debug { "Sending transaction" }
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package co.electriccoin.zcash.ui.screen.send
|
||||||
|
|
||||||
|
/**
|
||||||
|
* These are only used for automated testing.
|
||||||
|
*/
|
||||||
|
object SendTag {
|
||||||
|
const val SEND_FORM_BUTTON = "send_form_button"
|
||||||
|
const val SEND_CONFIRMATION_BUTTON = "send_confirmation_button"
|
||||||
|
const val SEND_FAILED_BUTTON = "send_failed_button"
|
||||||
|
const val SEND_SUCCESS_BUTTON = "send_success_button"
|
||||||
|
}
|
|
@ -14,14 +14,11 @@ import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
|
||||||
import androidx.compose.material.icons.outlined.QrCodeScanner
|
import androidx.compose.material.icons.outlined.QrCodeScanner
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
@ -30,6 +27,8 @@ import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
@ -46,14 +45,17 @@ import cash.z.ecc.android.sdk.model.toZecString
|
||||||
import cash.z.ecc.sdk.fixture.MemoFixture
|
import cash.z.ecc.sdk.fixture.MemoFixture
|
||||||
import cash.z.ecc.sdk.fixture.ZatoshiFixture
|
import cash.z.ecc.sdk.fixture.ZatoshiFixture
|
||||||
import co.electriccoin.zcash.ui.R
|
import co.electriccoin.zcash.ui.R
|
||||||
|
import co.electriccoin.zcash.ui.common.test.CommonTag
|
||||||
import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT
|
import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT
|
||||||
import co.electriccoin.zcash.ui.design.component.Body
|
import co.electriccoin.zcash.ui.design.component.Body
|
||||||
import co.electriccoin.zcash.ui.design.component.FormTextField
|
import co.electriccoin.zcash.ui.design.component.FormTextField
|
||||||
import co.electriccoin.zcash.ui.design.component.GradientSurface
|
import co.electriccoin.zcash.ui.design.component.GradientSurface
|
||||||
import co.electriccoin.zcash.ui.design.component.Header
|
import co.electriccoin.zcash.ui.design.component.Header
|
||||||
import co.electriccoin.zcash.ui.design.component.PrimaryButton
|
import co.electriccoin.zcash.ui.design.component.PrimaryButton
|
||||||
|
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
|
||||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme.dimens
|
import co.electriccoin.zcash.ui.design.theme.ZcashTheme.dimens
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.SendTag
|
||||||
import co.electriccoin.zcash.ui.screen.send.ext.ABBREVIATION_INDEX
|
import co.electriccoin.zcash.ui.screen.send.ext.ABBREVIATION_INDEX
|
||||||
import co.electriccoin.zcash.ui.screen.send.ext.abbreviated
|
import co.electriccoin.zcash.ui.screen.send.ext.abbreviated
|
||||||
import co.electriccoin.zcash.ui.screen.send.ext.valueOrEmptyChar
|
import co.electriccoin.zcash.ui.screen.send.ext.valueOrEmptyChar
|
||||||
|
@ -76,6 +78,7 @@ private fun PreviewSendForm() {
|
||||||
onCreateAndSend = {},
|
onCreateAndSend = {},
|
||||||
onQrScannerOpen = {},
|
onQrScannerOpen = {},
|
||||||
onBack = {},
|
onBack = {},
|
||||||
|
onSettings = {},
|
||||||
hasCameraFeature = true
|
hasCameraFeature = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -123,7 +126,7 @@ private fun PreviewSendFailure() {
|
||||||
private fun PreviewSendConfirmation() {
|
private fun PreviewSendConfirmation() {
|
||||||
ZcashTheme(forceDarkMode = false) {
|
ZcashTheme(forceDarkMode = false) {
|
||||||
GradientSurface {
|
GradientSurface {
|
||||||
Confirmation(
|
SendConfirmation(
|
||||||
zecSend =
|
zecSend =
|
||||||
ZecSend(
|
ZecSend(
|
||||||
destination = runBlocking { WalletAddressFixture.sapling() },
|
destination = runBlocking { WalletAddressFixture.sapling() },
|
||||||
|
@ -146,6 +149,7 @@ fun Send(
|
||||||
zecSend: ZecSend?,
|
zecSend: ZecSend?,
|
||||||
onZecSendChange: (ZecSend) -> Unit,
|
onZecSendChange: (ZecSend) -> Unit,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
|
onSettings: () -> Unit,
|
||||||
onCreateAndSend: (ZecSend) -> Unit,
|
onCreateAndSend: (ZecSend) -> Unit,
|
||||||
onQrScannerOpen: () -> Unit,
|
onQrScannerOpen: () -> Unit,
|
||||||
hasCameraFeature: Boolean
|
hasCameraFeature: Boolean
|
||||||
|
@ -153,7 +157,8 @@ fun Send(
|
||||||
Scaffold(topBar = {
|
Scaffold(topBar = {
|
||||||
SendTopAppBar(
|
SendTopAppBar(
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
showBackNavigationButton = sendStage != SendStage.Sending
|
onSettings = onSettings,
|
||||||
|
showBackNavigationButton = (sendStage != SendStage.Sending && sendStage != SendStage.Form)
|
||||||
)
|
)
|
||||||
}) { paddingValues ->
|
}) { paddingValues ->
|
||||||
SendMainContent(
|
SendMainContent(
|
||||||
|
@ -184,28 +189,33 @@ fun Send(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
private fun SendTopAppBar(
|
private fun SendTopAppBar(
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
|
onSettings: () -> Unit,
|
||||||
showBackNavigationButton: Boolean = true
|
showBackNavigationButton: Boolean = true
|
||||||
) {
|
) {
|
||||||
if (showBackNavigationButton) {
|
SmallTopAppBar(
|
||||||
TopAppBar(
|
titleText = stringResource(id = R.string.send_title),
|
||||||
title = { Text(text = stringResource(id = R.string.send_title)) },
|
onBack = onBack,
|
||||||
navigationIcon = {
|
backText =
|
||||||
IconButton(
|
if (showBackNavigationButton) {
|
||||||
onClick = onBack
|
stringResource(id = R.string.send_back)
|
||||||
) {
|
} else {
|
||||||
Icon(
|
null
|
||||||
imageVector = Icons.Filled.ArrowBack,
|
},
|
||||||
contentDescription = stringResource(R.string.send_back_content_description)
|
backContentDescriptionText = stringResource(id = R.string.send_back_content_description),
|
||||||
)
|
hamburgerMenuActions = {
|
||||||
}
|
IconButton(
|
||||||
|
onClick = onSettings,
|
||||||
|
modifier = Modifier.testTag(CommonTag.SETTINGS_TOP_BAR_BUTTON)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = co.electriccoin.zcash.ui.design.R.drawable.hamburger_menu_icon),
|
||||||
|
contentDescription = stringResource(id = R.string.settings_menu_content_description)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
} else {
|
)
|
||||||
TopAppBar(title = { Text(text = stringResource(id = R.string.send_title)) })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
|
@ -239,7 +249,7 @@ private fun SendMainContent(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
(sendStage == SendStage.Confirmation) -> {
|
(sendStage == SendStage.Confirmation) -> {
|
||||||
Confirmation(
|
SendConfirmation(
|
||||||
zecSend = zecSend,
|
zecSend = zecSend,
|
||||||
onConfirmation = {
|
onConfirmation = {
|
||||||
onSendStageChange(SendStage.Sending)
|
onSendStageChange(SendStage.Sending)
|
||||||
|
@ -426,13 +436,14 @@ private fun SendForm(
|
||||||
// Check for ABBREVIATION_INDEX goes away once proper address validation is in place.
|
// Check for ABBREVIATION_INDEX goes away once proper address validation is in place.
|
||||||
// For now, it just prevents a crash on the confirmation screen.
|
// For now, it just prevents a crash on the confirmation screen.
|
||||||
enabled = amountZecString.isNotBlank() && recipientAddressString.length > ABBREVIATION_INDEX,
|
enabled = amountZecString.isNotBlank() && recipientAddressString.length > ABBREVIATION_INDEX,
|
||||||
outerPaddingValues = PaddingValues(top = dimens.spacingNone)
|
outerPaddingValues = PaddingValues(top = dimens.spacingNone),
|
||||||
|
modifier = Modifier.testTag(SendTag.SEND_FORM_BUTTON)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun Confirmation(
|
private fun SendConfirmation(
|
||||||
zecSend: ZecSend,
|
zecSend: ZecSend,
|
||||||
onConfirmation: () -> Unit,
|
onConfirmation: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
|
@ -465,7 +476,10 @@ private fun Confirmation(
|
||||||
)
|
)
|
||||||
|
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
modifier = Modifier.padding(top = dimens.spacingSmall),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(top = dimens.spacingSmall)
|
||||||
|
.testTag(SendTag.SEND_CONFIRMATION_BUTTON),
|
||||||
onClick = onConfirmation,
|
onClick = onConfirmation,
|
||||||
text = stringResource(id = R.string.send_confirmation_button),
|
text = stringResource(id = R.string.send_confirmation_button),
|
||||||
outerPaddingValues = PaddingValues(top = dimens.spacingSmall)
|
outerPaddingValues = PaddingValues(top = dimens.spacingSmall)
|
||||||
|
@ -566,7 +580,10 @@ private fun SendSuccessful(
|
||||||
)
|
)
|
||||||
|
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
modifier = Modifier.padding(top = dimens.spacingSmall),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(top = dimens.spacingSmall)
|
||||||
|
.testTag(SendTag.SEND_SUCCESS_BUTTON),
|
||||||
text = stringResource(R.string.send_successful_button),
|
text = stringResource(R.string.send_successful_button),
|
||||||
onClick = onDone,
|
onClick = onDone,
|
||||||
outerPaddingValues = PaddingValues(top = dimens.spacingSmall)
|
outerPaddingValues = PaddingValues(top = dimens.spacingSmall)
|
||||||
|
@ -616,7 +633,10 @@ private fun SendFailure(
|
||||||
)
|
)
|
||||||
|
|
||||||
PrimaryButton(
|
PrimaryButton(
|
||||||
modifier = Modifier.padding(top = dimens.spacingSmall),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(top = dimens.spacingSmall)
|
||||||
|
.testTag(SendTag.SEND_FAILED_BUTTON),
|
||||||
text = stringResource(R.string.send_failure_button),
|
text = stringResource(R.string.send_failure_button),
|
||||||
onClick = onDone,
|
onClick = onDone,
|
||||||
outerPaddingValues = PaddingValues(top = dimens.spacingSmall)
|
outerPaddingValues = PaddingValues(top = dimens.spacingSmall)
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
package co.electriccoin.zcash.ui.screen.settings
|
package co.electriccoin.zcash.ui.screen.settings
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
@ -11,6 +12,7 @@ import co.electriccoin.zcash.ui.common.model.VersionInfo
|
||||||
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
||||||
import co.electriccoin.zcash.ui.configuration.ConfigurationEntries
|
import co.electriccoin.zcash.ui.configuration.ConfigurationEntries
|
||||||
import co.electriccoin.zcash.ui.configuration.RemoteConfig
|
import co.electriccoin.zcash.ui.configuration.RemoteConfig
|
||||||
|
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
|
||||||
import co.electriccoin.zcash.ui.screen.settings.model.TroubleshootingParameters
|
import co.electriccoin.zcash.ui.screen.settings.model.TroubleshootingParameters
|
||||||
import co.electriccoin.zcash.ui.screen.settings.view.Settings
|
import co.electriccoin.zcash.ui.screen.settings.view.Settings
|
||||||
import co.electriccoin.zcash.ui.screen.settings.viewmodel.SettingsViewModel
|
import co.electriccoin.zcash.ui.screen.settings.viewmodel.SettingsViewModel
|
||||||
|
@ -60,13 +62,18 @@ private fun WrapSettings(
|
||||||
val isKeepScreenOnWhileSyncing = settingsViewModel.isKeepScreenOnWhileSyncing.collectAsStateWithLifecycle().value
|
val isKeepScreenOnWhileSyncing = settingsViewModel.isKeepScreenOnWhileSyncing.collectAsStateWithLifecycle().value
|
||||||
val isAnalyticsEnabled = settingsViewModel.isAnalyticsEnabled.collectAsStateWithLifecycle().value
|
val isAnalyticsEnabled = settingsViewModel.isAnalyticsEnabled.collectAsStateWithLifecycle().value
|
||||||
|
|
||||||
|
BackHandler {
|
||||||
|
goBack()
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("ComplexCondition")
|
@Suppress("ComplexCondition")
|
||||||
if (null == synchronizer ||
|
if (null == synchronizer ||
|
||||||
null == isAnalyticsEnabled ||
|
null == isAnalyticsEnabled ||
|
||||||
null == isBackgroundSyncEnabled ||
|
null == isBackgroundSyncEnabled ||
|
||||||
null == isKeepScreenOnWhileSyncing
|
null == isKeepScreenOnWhileSyncing
|
||||||
) {
|
) {
|
||||||
// Display loading indicator
|
// Improve this by allowing screen composition and updating it after the data is available
|
||||||
|
CircularScreenProgressIndicator()
|
||||||
} else {
|
} else {
|
||||||
Settings(
|
Settings(
|
||||||
TroubleshootingParameters(
|
TroubleshootingParameters(
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="account_menu_content_description">Open Settings</string>
|
|
||||||
<string name="account_button_receive">Receive</string>
|
|
||||||
<string name="account_button_send">Send</string>
|
|
||||||
<string name="account_button_history">Transaction History</string>
|
<string name="account_button_history">Transaction History</string>
|
||||||
|
|
||||||
<string name="account_status_syncing_format" formatted="true">Syncing - <xliff:g id="synced_percent" example="50.25">
|
<string name="account_status_syncing_format" formatted="true">Syncing - <xliff:g id="synced_percent" example="50.25">
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<string name="balances_title">Balances</string>
|
||||||
|
</resources>
|
|
@ -3,4 +3,5 @@
|
||||||
<string name="empty_char">-</string>
|
<string name="empty_char">-</string>
|
||||||
<string name="zcash_logo_content_description">Zcash logo</string>
|
<string name="zcash_logo_content_description">Zcash logo</string>
|
||||||
<string name="not_implemented_yet">Not implemented yet.</string>
|
<string name="not_implemented_yet">Not implemented yet.</string>
|
||||||
|
<string name="settings_menu_content_description">Open Settings</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1,24 +1,8 @@
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="home_menu_content_description">Open Settings</string>
|
|
||||||
<string name="home_button_receive">Receive</string>
|
|
||||||
<string name="home_button_send">Send</string>
|
|
||||||
<string name="home_button_history">Transaction History</string>
|
<string name="home_button_history">Transaction History</string>
|
||||||
|
|
||||||
<string name="home_status_syncing_format" formatted="true">Syncing - <xliff:g id="synced_percent" example="50.25">
|
<string name="home_tab_account">Account</string>
|
||||||
%1$s</xliff:g>%%</string> <!-- double %% for escaping -->
|
<string name="home_tab_send">Send</string>
|
||||||
<string name="home_status_syncing_catchup">Syncing</string>
|
<string name="home_tab_receive">Receive</string>
|
||||||
<string name="home_status_syncing_amount_suffix" formatted="true"><xliff:g id="amount_prefix" example="123$">%1$s</xliff:g> so far</string>
|
<string name="home_tab_balances">Balances</string>
|
||||||
<string name="home_status_syncing_additional_information">We will show you funds as we discover them.</string>
|
|
||||||
<string name="home_status_up_to_date">Up-to-date</string>
|
|
||||||
<string name="home_status_sending_format" formatted="true">Sending <xliff:g id="sending_amount" example=".023">%1$s</xliff:g></string>
|
|
||||||
<string name="home_status_receiving_format" formatted="true">Receiving <xliff:g id="receiving_amount" example=".023">%1$s</xliff:g> ZEC</string>
|
|
||||||
<string name="home_status_shielding_format" formatted="true">Shielding <xliff:g id="shielding_amount" example=".023">%1$s</xliff:g> ZEC</string>
|
|
||||||
<string name="home_status_update">Please Update</string>
|
|
||||||
<string name="home_status_error" formatted="true">Error: <xliff:g id="error_type" example="Lost connection">%1$s</xliff:g></string>
|
|
||||||
<string name="home_status_error_connection">Disconnected</string>
|
|
||||||
<string name="home_status_error_unknown">Unknown cause</string>
|
|
||||||
<string name="home_status_stopped">Synchronizer stopped</string>
|
|
||||||
<string name="home_status_updating_blockheight">Updating blockheight</string>
|
|
||||||
<string name="home_status_fiat_currency_price_out_of_date" formatted="true"><xliff:g id="fiat_currency" example="USD">%1$s</xliff:g> price out-of-date</string>
|
|
||||||
<string name="home_status_spendable" formatted="true">Fully spendable in <xliff:g id="spendable_time" example="2 minutes">%1$s</xliff:g></string>
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="receive_title">Receive</string>
|
<string name="receive_title">Receive</string>
|
||||||
<string name="receive_back">Back</string>
|
|
||||||
<string name="receive_back_content_description">Back</string>
|
|
||||||
<string name="receive_brightness_content_description">Adjust brightness</string>
|
<string name="receive_brightness_content_description">Adjust brightness</string>
|
||||||
<string name="receive_qr_code_content_description">QR code for address</string>
|
<string name="receive_qr_code_content_description">QR code for address</string>
|
||||||
<string name="receive_caption">Your Address</string>
|
<string name="receive_caption">Your Address</string>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="send_title">Send ZEC</string>
|
<string name="send_title">Send</string>
|
||||||
|
<string name="send_back">Back</string>
|
||||||
<string name="send_back_content_description">Back</string>
|
<string name="send_back_content_description">Back</string>
|
||||||
<string name="send_scan_content_description">Scan</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" formatted="true"><xliff:g id="balance" example="12.345">%1$s</xliff:g> ZEC Available</string>
|
||||||
|
|
|
@ -7,9 +7,10 @@ import android.os.Build
|
||||||
import android.os.LocaleList
|
import android.os.LocaleList
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.compose.ui.test.assertIsEnabled
|
import androidx.compose.ui.test.assertIsEnabled
|
||||||
import androidx.compose.ui.test.hasTestTag
|
import androidx.compose.ui.test.hasContentDescription
|
||||||
import androidx.compose.ui.test.hasText
|
import androidx.compose.ui.test.hasText
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||||
|
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||||
import androidx.compose.ui.test.junit4.ComposeTestRule
|
import androidx.compose.ui.test.junit4.ComposeTestRule
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
import androidx.compose.ui.test.onNodeWithTag
|
import androidx.compose.ui.test.onNodeWithTag
|
||||||
|
@ -40,9 +41,11 @@ import co.electriccoin.zcash.ui.R
|
||||||
import co.electriccoin.zcash.ui.common.viewmodel.SecretState
|
import co.electriccoin.zcash.ui.common.viewmodel.SecretState
|
||||||
import co.electriccoin.zcash.ui.design.component.ConfigurationOverride
|
import co.electriccoin.zcash.ui.design.component.ConfigurationOverride
|
||||||
import co.electriccoin.zcash.ui.design.component.UiMode
|
import co.electriccoin.zcash.ui.design.component.UiMode
|
||||||
|
import co.electriccoin.zcash.ui.screen.account.AccountTag
|
||||||
|
import co.electriccoin.zcash.ui.screen.home.HomeTag
|
||||||
import co.electriccoin.zcash.ui.screen.restore.RestoreTag
|
import co.electriccoin.zcash.ui.screen.restore.RestoreTag
|
||||||
import co.electriccoin.zcash.ui.screen.restore.viewmodel.RestoreViewModel
|
import co.electriccoin.zcash.ui.screen.restore.viewmodel.RestoreViewModel
|
||||||
import co.electriccoin.zcash.ui.screen.settings.SettingsTag
|
import co.electriccoin.zcash.ui.screen.send.SendTag
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
@ -95,6 +98,13 @@ class ScreenshotTest : UiTestPrerequisites() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun ComposeContentTestRule.navigateInHomeTab(destinationTag: String) {
|
||||||
|
onNodeWithTag(destinationTag).also {
|
||||||
|
it.assertExists()
|
||||||
|
it.performClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun runWith(
|
private fun runWith(
|
||||||
uiMode: UiMode,
|
uiMode: UiMode,
|
||||||
locale: String,
|
locale: String,
|
||||||
|
@ -265,17 +275,22 @@ class ScreenshotTest : UiTestPrerequisites() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// These are the home screen bottom navigation sub-screens
|
||||||
onboardingScreenshots(resContext, tag, composeTestRule)
|
onboardingScreenshots(resContext, tag, composeTestRule)
|
||||||
recoveryScreenshots(resContext, tag, composeTestRule)
|
recoveryScreenshots(resContext, tag, composeTestRule)
|
||||||
homeScreenshots(resContext, tag, composeTestRule)
|
|
||||||
|
|
||||||
// These are the buttons on the home screen
|
composeTestRule.navigateInHomeTab(HomeTag.TAB_ACCOUNT)
|
||||||
navigateTo(NavigationTargets.SEND)
|
accountScreenshots(tag, composeTestRule)
|
||||||
|
|
||||||
|
composeTestRule.navigateInHomeTab(HomeTag.TAB_SEND)
|
||||||
sendZecScreenshots(resContext, tag, composeTestRule)
|
sendZecScreenshots(resContext, tag, composeTestRule)
|
||||||
|
|
||||||
navigateTo(NavigationTargets.RECEIVE)
|
composeTestRule.navigateInHomeTab(HomeTag.TAB_RECEIVE)
|
||||||
receiveZecScreenshots(resContext, tag, composeTestRule)
|
receiveZecScreenshots(resContext, tag, composeTestRule)
|
||||||
|
|
||||||
|
composeTestRule.navigateInHomeTab(HomeTag.TAB_BALANCES)
|
||||||
|
balancesScreenshots(resContext, tag, composeTestRule)
|
||||||
|
|
||||||
navigateTo(NavigationTargets.WALLET_ADDRESS_DETAILS)
|
navigateTo(NavigationTargets.WALLET_ADDRESS_DETAILS)
|
||||||
addressDetailsScreenshots(resContext, tag, composeTestRule)
|
addressDetailsScreenshots(resContext, tag, composeTestRule)
|
||||||
|
|
||||||
|
@ -283,7 +298,7 @@ class ScreenshotTest : UiTestPrerequisites() {
|
||||||
transactionHistoryScreenshots(resContext, tag, composeTestRule)
|
transactionHistoryScreenshots(resContext, tag, composeTestRule)
|
||||||
|
|
||||||
navigateTo(NavigationTargets.SETTINGS)
|
navigateTo(NavigationTargets.SETTINGS)
|
||||||
settingsScreenshots(tag, composeTestRule)
|
settingsScreenshots(resContext, tag, composeTestRule)
|
||||||
|
|
||||||
// These are the Settings screen items
|
// These are the Settings screen items
|
||||||
// We could manually click on each one, which is a better integration test but a worse screenshot test
|
// We could manually click on each one, which is a better integration test but a worse screenshot test
|
||||||
|
@ -362,8 +377,7 @@ private fun recoveryScreenshots(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun homeScreenshots(
|
private fun accountScreenshots(
|
||||||
resContext: Context,
|
|
||||||
tag: String,
|
tag: String,
|
||||||
composeTestRule: AndroidComposeTestRule<ActivityScenarioRule<MainActivity>, MainActivity>
|
composeTestRule: AndroidComposeTestRule<ActivityScenarioRule<MainActivity>, MainActivity>
|
||||||
) {
|
) {
|
||||||
|
@ -374,17 +388,32 @@ private fun homeScreenshots(
|
||||||
composeTestRule.activity.walletViewModel.walletSnapshot.value != null
|
composeTestRule.activity.walletViewModel.walletSnapshot.value != null
|
||||||
}
|
}
|
||||||
|
|
||||||
composeTestRule.onNode(hasText(resContext.getString(R.string.home_button_send), ignoreCase = true)).also {
|
composeTestRule.onNodeWithTag(AccountTag.STATUS_VIEWS).also {
|
||||||
it.assertExists()
|
it.assertExists()
|
||||||
ScreenshotTest.takeScreenshot(tag, "Home 1")
|
ScreenshotTest.takeScreenshot(tag, "Account 1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun balancesScreenshots(
|
||||||
|
resContext: Context,
|
||||||
|
tag: String,
|
||||||
|
composeTestRule: AndroidComposeTestRule<ActivityScenarioRule<MainActivity>, MainActivity>
|
||||||
|
) {
|
||||||
|
// TODO [#1127]: Implement Balances screen
|
||||||
|
// TODO [#1127]: https://github.com/Electric-Coin-Company/zashi-android/issues/1127
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithText(resContext.getString(R.string.not_implemented_yet)).also {
|
||||||
|
it.assertExists()
|
||||||
|
ScreenshotTest.takeScreenshot(tag, "Balances 1")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun settingsScreenshots(
|
private fun settingsScreenshots(
|
||||||
|
resContext: Context,
|
||||||
tag: String,
|
tag: String,
|
||||||
composeTestRule: ComposeTestRule
|
composeTestRule: ComposeTestRule
|
||||||
) {
|
) {
|
||||||
composeTestRule.onNode(hasTestTag(SettingsTag.SETTINGS_TOP_APP_BAR)).also {
|
composeTestRule.onNode(hasText(resContext.getString(R.string.settings_backup_wallet), ignoreCase = true)).also {
|
||||||
it.assertExists()
|
it.assertExists()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -438,19 +467,16 @@ private fun receiveZecScreenshots(
|
||||||
composeTestRule.activity.walletViewModel.addresses.value != null
|
composeTestRule.activity.walletViewModel.addresses.value != null
|
||||||
}
|
}
|
||||||
|
|
||||||
composeTestRule.onNode(hasText(resContext.getString(R.string.receive_title), ignoreCase = true)).also {
|
composeTestRule.onNode(
|
||||||
|
hasContentDescription(
|
||||||
|
value = resContext.getString(R.string.receive_qr_code_content_description),
|
||||||
|
ignoreCase = true
|
||||||
|
)
|
||||||
|
).also {
|
||||||
it.assertExists()
|
it.assertExists()
|
||||||
}
|
}
|
||||||
|
|
||||||
ScreenshotTest.takeScreenshot(tag, "Receive 1")
|
ScreenshotTest.takeScreenshot(tag, "Receive 1")
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(resContext.getString(R.string.receive_see_address_details), ignoreCase = true).also {
|
|
||||||
it.performClick()
|
|
||||||
}
|
|
||||||
|
|
||||||
composeTestRule.waitForIdle()
|
|
||||||
|
|
||||||
ScreenshotTest.takeScreenshot(tag, "Address details")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendZecScreenshots(
|
private fun sendZecScreenshots(
|
||||||
|
@ -495,7 +521,7 @@ private fun sendZecScreenshots(
|
||||||
// Screenshot: Fulfilled form
|
// Screenshot: Fulfilled form
|
||||||
ScreenshotTest.takeScreenshot(tag, "Send 2")
|
ScreenshotTest.takeScreenshot(tag, "Send 2")
|
||||||
|
|
||||||
composeTestRule.onNodeWithText(resContext.getString(R.string.send_create), ignoreCase = true).also {
|
composeTestRule.onNodeWithTag(SendTag.SEND_FORM_BUTTON).also {
|
||||||
it.performClick()
|
it.performClick()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue