[#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]
|
||||
|
||||
### Changed
|
||||
- Home screen navigation switched from the Side menu to the Bottom Navigation Tabs menu
|
||||
|
||||
## [0.2.0 (505)] - 2023-12-11
|
||||
|
||||
### Added
|
||||
|
|
|
@ -3,4 +3,5 @@ package co.electriccoin.zcash.ui.design.component
|
|||
object CommonTag {
|
||||
const val CHIP_LAYOUT = "chip_layout"
|
||||
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
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.basicMarquee
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
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.res.stringResource
|
||||
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.TextDecoration
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
|
@ -178,6 +183,7 @@ fun Reference(
|
|||
* @param amount of ZECs to be displayed
|
||||
* @param modifier to modify the Text UI element as needed
|
||||
*/
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun HeaderWithZecIcon(
|
||||
amount: String,
|
||||
|
@ -187,7 +193,8 @@ fun HeaderWithZecIcon(
|
|||
text = stringResource(R.string.amount_with_zec_currency_symbol, amount),
|
||||
style = ZcashTheme.extendedTypography.zecBalance,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
modifier = modifier
|
||||
maxLines = 1,
|
||||
modifier = Modifier.basicMarquee().then(modifier)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -203,3 +210,25 @@ fun BodyWithFiatCurrencySymbol(
|
|||
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.shape.RoundedCornerShape
|
||||
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.MoreVert
|
||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||
|
@ -30,6 +31,7 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
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
|
||||
private fun TopBarHamburgerMenuExample(
|
||||
modifier: Modifier = Modifier,
|
||||
|
@ -236,7 +263,13 @@ fun SmallTopAppBar(
|
|||
}
|
||||
}
|
||||
},
|
||||
actions = hamburgerMenuActions ?: regularActions ?: {},
|
||||
modifier = modifier
|
||||
actions = {
|
||||
regularActions?.invoke(this)
|
||||
hamburgerMenuActions?.invoke(this)
|
||||
},
|
||||
modifier =
|
||||
Modifier
|
||||
.testTag(CommonTag.TOP_APP_BAR)
|
||||
.then(modifier)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -28,6 +28,8 @@ data class Dimens(
|
|||
// Chip
|
||||
val chipShadowElevation: Dp,
|
||||
val chipStroke: Dp,
|
||||
// Progress
|
||||
val circularScreenProgressWidth: Dp,
|
||||
// TopAppBar:
|
||||
val topAppBarZcashLogoHeight: Dp,
|
||||
val topAppBarActionRippleCorner: Dp,
|
||||
|
@ -59,6 +61,7 @@ private val defaultDimens =
|
|||
buttonHeight = 50.dp,
|
||||
chipShadowElevation = 4.dp,
|
||||
chipStroke = 0.5.dp,
|
||||
circularScreenProgressWidth = 48.dp,
|
||||
topAppBarZcashLogoHeight = 24.dp,
|
||||
topAppBarActionRippleCorner = 28.dp,
|
||||
textFieldDefaultHeight = 215.dp,
|
||||
|
|
|
@ -35,6 +35,9 @@ data class ExtendedColors(
|
|||
val screenTitleColor: Color,
|
||||
val aboutTextColor: Color,
|
||||
val welcomeAnimationColor: Color,
|
||||
val complementaryColor: Color,
|
||||
val dividerColor: Color,
|
||||
val tabTextColor: Color,
|
||||
) {
|
||||
@Composable
|
||||
fun surfaceGradient() =
|
||||
|
|
|
@ -11,6 +11,9 @@ import co.electriccoin.zcash.ui.design.theme.ExtendedColors
|
|||
// TODO [#998]: Check and enhance screen dark mode
|
||||
// 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 {
|
||||
val backgroundStart = Color(0xFF000000)
|
||||
val backgroundEnd = Color(0xFF000000)
|
||||
|
@ -65,12 +68,13 @@ internal object Dark {
|
|||
|
||||
val buttonShadowColor = Color(0xFFFFFFFF)
|
||||
|
||||
// to be added later
|
||||
// Proper values will be added later, see #998
|
||||
val aboutTextColor = Color.Unspecified
|
||||
|
||||
val screenTitleColor = Color(0xFF040404)
|
||||
|
||||
val welcomeAnimationColor = Color(0xFF231F20)
|
||||
val complementaryColor = Color(0xFFF4B728)
|
||||
val dividerColor = Color(0xFFDDDDDD)
|
||||
val tabTextColor = Color(0xFF040404)
|
||||
}
|
||||
|
||||
internal object Light {
|
||||
|
@ -131,10 +135,11 @@ internal object Light {
|
|||
val buttonShadowColor = Color(0xFF000000)
|
||||
|
||||
val screenTitleColor = Color(0xFF040404)
|
||||
|
||||
val aboutTextColor = Color(0xFF4E4E4E)
|
||||
|
||||
val welcomeAnimationColor = Color(0xFF231F20)
|
||||
val complementaryColor = Color(0xFFF4B728)
|
||||
val dividerColor = Color(0xFFDDDDDD)
|
||||
val tabTextColor = Color(0xFF040404)
|
||||
}
|
||||
|
||||
internal val DarkColorPalette =
|
||||
|
@ -189,7 +194,10 @@ internal val DarkExtendedColorPalette =
|
|||
buttonShadowColor = Dark.buttonShadowColor,
|
||||
screenTitleColor = Dark.screenTitleColor,
|
||||
aboutTextColor = Dark.aboutTextColor,
|
||||
welcomeAnimationColor = Dark.welcomeAnimationColor
|
||||
welcomeAnimationColor = Dark.welcomeAnimationColor,
|
||||
complementaryColor = Dark.complementaryColor,
|
||||
dividerColor = Dark.dividerColor,
|
||||
tabTextColor = Dark.tabTextColor,
|
||||
)
|
||||
|
||||
internal val LightExtendedColorPalette =
|
||||
|
@ -220,7 +228,10 @@ internal val LightExtendedColorPalette =
|
|||
buttonShadowColor = Light.buttonShadowColor,
|
||||
screenTitleColor = Light.screenTitleColor,
|
||||
aboutTextColor = Light.aboutTextColor,
|
||||
welcomeAnimationColor = Light.welcomeAnimationColor
|
||||
welcomeAnimationColor = Light.welcomeAnimationColor,
|
||||
complementaryColor = Light.complementaryColor,
|
||||
dividerColor = Light.dividerColor,
|
||||
tabTextColor = Dark.tabTextColor,
|
||||
)
|
||||
|
||||
@Suppress("CompositionLocalAllowlist")
|
||||
|
@ -254,5 +265,8 @@ internal val LocalExtendedColors =
|
|||
screenTitleColor = Color.Unspecified,
|
||||
aboutTextColor = 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.googlefonts.Font
|
||||
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.unit.sp
|
||||
import co.electriccoin.zcash.ui.design.R
|
||||
|
@ -129,6 +128,12 @@ internal val SecondaryTypography =
|
|||
fontFamily = ArchivoFontFamily,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 16.sp
|
||||
),
|
||||
labelMedium =
|
||||
TextStyle(
|
||||
fontFamily = ArchivoFontFamily,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 12.sp
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -140,7 +145,6 @@ data class Typography(
|
|||
|
||||
@Immutable
|
||||
data class ExtendedTypography(
|
||||
val chipIndex: TextStyle,
|
||||
val listItem: TextStyle,
|
||||
val zecBalance: TextStyle,
|
||||
val aboutText: TextStyle,
|
||||
|
@ -150,6 +154,7 @@ data class ExtendedTypography(
|
|||
val textFieldHint: TextStyle,
|
||||
val textFieldValue: TextStyle,
|
||||
val textFieldBirthday: TextStyle,
|
||||
val textNavTab: TextStyle,
|
||||
)
|
||||
|
||||
@Suppress("CompositionLocalAllowlist")
|
||||
|
@ -165,12 +170,6 @@ val LocalTypographies =
|
|||
val LocalExtendedTypography =
|
||||
staticCompositionLocalOf {
|
||||
ExtendedTypography(
|
||||
chipIndex =
|
||||
PrimaryTypography.bodyLarge.copy(
|
||||
fontSize = 10.sp,
|
||||
baselineShift = BaselineShift.Superscript,
|
||||
fontWeight = FontWeight.Bold
|
||||
),
|
||||
listItem =
|
||||
PrimaryTypography.bodyLarge.copy(
|
||||
fontSize = 24.sp
|
||||
|
@ -209,5 +208,9 @@ val LocalExtendedTypography =
|
|||
fontSize = 17.sp,
|
||||
),
|
||||
textFieldBirthday = SecondaryTypography.headlineMedium.copy(),
|
||||
textNavTab =
|
||||
SecondaryTypography.labelSmall.copy(
|
||||
fontSize = 13.sp
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -31,11 +31,12 @@ android {
|
|||
setOf(
|
||||
"src/main/res/ui/about",
|
||||
"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/export_data",
|
||||
"src/main/res/ui/history",
|
||||
"src/main/res/ui/home",
|
||||
"src/main/res/ui/new_wallet_recovery",
|
||||
"src/main/res/ui/onboarding",
|
||||
"src/main/res/ui/receive",
|
||||
"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 cash.z.ecc.android.sdk.Synchronizer
|
||||
|
@ -37,7 +37,7 @@ class WalletDisplayValuesTest {
|
|||
assertNotNull(values)
|
||||
assertEquals(1f, values.progress.decimal)
|
||||
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]: https://github.com/Electric-Coin-Company/zcash-android-wallet-sdk/issues/578
|
||||
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
|
||||
|
||||
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.home.view.Home
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
class HomeTestSetup(
|
||||
private val composeTestRule: ComposeContentTestRule,
|
||||
private val walletSnapshot: WalletSnapshot,
|
||||
private val isShowFiatConversion: Boolean
|
||||
) {
|
||||
private val onSettingsCount = AtomicInteger(0)
|
||||
private val onReceiveCount = AtomicInteger(0)
|
||||
private val onAccountsCount = 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()
|
||||
return onSettingsCount.get()
|
||||
}
|
||||
|
||||
fun getOnReceiveCount(): Int {
|
||||
composeTestRule.waitForIdle()
|
||||
return onReceiveCount.get()
|
||||
return onAccountsCount.get()
|
||||
}
|
||||
|
||||
fun getOnSendCount(): Int {
|
||||
|
@ -32,37 +21,27 @@ class HomeTestSetup(
|
|||
return onSendCount.get()
|
||||
}
|
||||
|
||||
fun getOnHistoryCount(): Int {
|
||||
fun getOnReceiveCount(): Int {
|
||||
composeTestRule.waitForIdle()
|
||||
return onHistoryCount.get()
|
||||
return onReceiveCount.get()
|
||||
}
|
||||
|
||||
fun getWalletSnapshot(): WalletSnapshot {
|
||||
fun getOnBalancesCount(): Int {
|
||||
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
|
||||
@Suppress("TestFunctionName")
|
||||
fun DefaultContent() {
|
||||
Home(
|
||||
walletSnapshot,
|
||||
isUpdateAvailable = false,
|
||||
isKeepScreenOnDuringSync = false,
|
||||
isFiatConversionEnabled = isShowFiatConversion,
|
||||
goSettings = {
|
||||
onSettingsCount.incrementAndGet()
|
||||
},
|
||||
goReceive = {
|
||||
onReceiveCount.incrementAndGet()
|
||||
},
|
||||
goSend = {
|
||||
onSendCount.incrementAndGet()
|
||||
},
|
||||
goHistory = {
|
||||
onHistoryCount.incrementAndGet()
|
||||
},
|
||||
)
|
||||
Home()
|
||||
}
|
||||
|
||||
fun setDefaultContent() {
|
||||
|
@ -72,4 +51,5 @@ class HomeTestSetup(
|
|||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
@ -1,28 +1,17 @@
|
|||
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.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.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.Test
|
||||
|
||||
class HomeViewIntegrationTest : UiTestPrerequisites() {
|
||||
@get:Rule
|
||||
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) =
|
||||
HomeTestSetup(
|
||||
composeTestRule,
|
||||
|
@ -30,7 +19,6 @@ class HomeViewIntegrationTest : UiTestPrerequisites() {
|
|||
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() {
|
||||
|
@ -60,9 +48,10 @@ class HomeViewIntegrationTest : UiTestPrerequisites() {
|
|||
assertNotEquals(WalletSnapshotFixture.PROGRESS, testSetup.getWalletSnapshot().progress)
|
||||
assertEquals(0.5f, testSetup.getWalletSnapshot().progress.decimal)
|
||||
|
||||
composeTestRule.onNodeWithTag(HomeTag.SINGLE_LINE_TEXT).also {
|
||||
composeTestRule.onNodeWithTag(AccountTag.SINGLE_LINE_TEXT).also {
|
||||
it.assertIsDisplayed()
|
||||
it.assertWidthIsAtLeast(1.dp)
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
@ -1,147 +1,40 @@
|
|||
@file:Suppress("UnusedPrivateMember")
|
||||
|
||||
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.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
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.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 org.junit.Assert.assertEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class HomeViewTest : UiTestPrerequisites() {
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
@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()
|
||||
}
|
||||
class HomeViewTest {
|
||||
// TODO [#1126]: Home screen view: Add view test
|
||||
// TODO [#1126]: https://github.com/Electric-Coin-Company/zashi-android/issues/1126
|
||||
}
|
||||
|
||||
private fun ComposeContentTestRule.openSettings() {
|
||||
onNodeWithContentDescription(getStringResource(R.string.home_menu_content_description)).also {
|
||||
it.performClick()
|
||||
}
|
||||
}
|
||||
|
||||
private fun ComposeContentTestRule.clickReceive() {
|
||||
onNodeWithText(getStringResource(R.string.home_button_receive), ignoreCase = true).also {
|
||||
private fun ComposeContentTestRule.clickAccount() {
|
||||
onNodeWithText(getStringResource(R.string.home_tab_account), ignoreCase = true).also {
|
||||
it.performClick()
|
||||
}
|
||||
}
|
||||
|
||||
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.performClick()
|
||||
}
|
||||
}
|
||||
|
||||
private fun ComposeContentTestRule.clickHistory() {
|
||||
onNodeWithText(getStringResource(R.string.home_button_history), ignoreCase = true).also {
|
||||
it.performScrollTo()
|
||||
private fun ComposeContentTestRule.clickReceive() {
|
||||
onNodeWithText(getStringResource(R.string.home_tab_receive), ignoreCase = true).also {
|
||||
it.performClick()
|
||||
}
|
||||
}
|
||||
|
||||
private fun ComposeContentTestRule.clickBalances() {
|
||||
onNodeWithText(getStringResource(R.string.home_tab_balances), ignoreCase = true).also {
|
||||
it.performClick()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,19 +38,19 @@ class ReceiveViewTest {
|
|||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun back() =
|
||||
fun click_settings_test() =
|
||||
runTest {
|
||||
val testSetup = newTestSetup(WalletAddressFixture.unified())
|
||||
|
||||
assertEquals(0, testSetup.getOnBackCount())
|
||||
assertEquals(0, testSetup.getOnSettingsCount())
|
||||
|
||||
composeTestRule.onNodeWithContentDescription(
|
||||
getStringResource(R.string.receive_back_content_description)
|
||||
getStringResource(R.string.settings_menu_content_description)
|
||||
).also {
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
assertEquals(1, testSetup.getOnBackCount())
|
||||
assertEquals(1, testSetup.getOnSettingsCount())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -19,7 +19,7 @@ class ReceiveViewTestSetup(
|
|||
private val composeTestRule: ComposeContentTestRule,
|
||||
walletAddress: WalletAddress
|
||||
) {
|
||||
private val onBackCount = AtomicInteger(0)
|
||||
private val onSettingsCount = AtomicInteger(0)
|
||||
private val onAddressDetailsCount = AtomicInteger(0)
|
||||
private val screenBrightness = ScreenBrightness()
|
||||
private val screenTimeout = ScreenTimeout()
|
||||
|
@ -34,9 +34,9 @@ class ReceiveViewTestSetup(
|
|||
return onAdjustBrightness.get()
|
||||
}
|
||||
|
||||
fun getOnBackCount(): Int {
|
||||
fun getOnSettingsCount(): Int {
|
||||
composeTestRule.waitForIdle()
|
||||
return onBackCount.get()
|
||||
return onSettingsCount.get()
|
||||
}
|
||||
|
||||
fun getOnAddressDetailsCount(): Int {
|
||||
|
@ -54,8 +54,8 @@ class ReceiveViewTestSetup(
|
|||
ZcashTheme {
|
||||
Receive(
|
||||
walletAddress,
|
||||
onBack = {
|
||||
onBackCount.getAndIncrement()
|
||||
onSettings = {
|
||||
onSettingsCount.getAndIncrement()
|
||||
},
|
||||
onAddressDetails = {
|
||||
onAddressDetailsCount.getAndIncrement()
|
||||
|
|
|
@ -4,6 +4,7 @@ import androidx.compose.ui.test.assertIsEnabled
|
|||
import androidx.compose.ui.test.assertIsNotEnabled
|
||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
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() {
|
||||
onNodeWithContentDescription(getStringResource(R.string.send_scan_content_description)).also {
|
||||
it.performClick()
|
||||
|
@ -70,25 +77,25 @@ internal fun ComposeContentTestRule.setMemo(memo: String) {
|
|||
}
|
||||
|
||||
internal fun ComposeContentTestRule.clickCreateAndSend() {
|
||||
onNodeWithText(getStringResource(R.string.send_create), ignoreCase = true).also {
|
||||
onNodeWithTag(SendTag.SEND_FORM_BUTTON).also {
|
||||
it.performClick()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun ComposeContentTestRule.clickConfirmation() {
|
||||
onNodeWithText(getStringResource(R.string.send_confirmation_button), ignoreCase = true).also {
|
||||
onNodeWithTag(SendTag.SEND_CONFIRMATION_BUTTON).also {
|
||||
it.performClick()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun ComposeContentTestRule.assertOnForm() {
|
||||
onNodeWithText(getStringResource(R.string.send_create), ignoreCase = true).also {
|
||||
onNodeWithTag(SendTag.SEND_FORM_BUTTON).also {
|
||||
it.assertExists()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun ComposeContentTestRule.assertOnConfirmation() {
|
||||
onNodeWithText(getStringResource(R.string.send_confirmation_button), ignoreCase = true).also {
|
||||
onNodeWithTag(SendTag.SEND_CONFIRMATION_BUTTON).also {
|
||||
it.assertExists()
|
||||
}
|
||||
}
|
||||
|
@ -112,13 +119,13 @@ internal fun ComposeContentTestRule.assertOnSendFailure() {
|
|||
}
|
||||
|
||||
internal fun ComposeContentTestRule.assertSendEnabled() {
|
||||
onNodeWithText(getStringResource(R.string.send_create), ignoreCase = true).also {
|
||||
onNodeWithTag(SendTag.SEND_FORM_BUTTON).also {
|
||||
it.assertIsEnabled()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun ComposeContentTestRule.assertSendDisabled() {
|
||||
onNodeWithText(getStringResource(R.string.send_create), ignoreCase = true).also {
|
||||
onNodeWithTag(SendTag.SEND_FORM_BUTTON).also {
|
||||
it.assertIsNotEnabled()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ class SendViewTestSetup(
|
|||
private val hasCameraFeature: Boolean
|
||||
) {
|
||||
private val onBackCount = AtomicInteger(0)
|
||||
private val onSettingsCount = AtomicInteger(0)
|
||||
private val onCreateCount = AtomicInteger(0)
|
||||
private val onScannerCount = AtomicInteger(0)
|
||||
val mutableActionExecuted = MutableStateFlow(false)
|
||||
|
@ -39,6 +40,11 @@ class SendViewTestSetup(
|
|||
return onBackCount.get()
|
||||
}
|
||||
|
||||
fun getOnSettingsCount(): Int {
|
||||
composeTestRule.waitForIdle()
|
||||
return onSettingsCount.get()
|
||||
}
|
||||
|
||||
fun getOnCreateCount(): Int {
|
||||
composeTestRule.waitForIdle()
|
||||
return onCreateCount.get()
|
||||
|
@ -96,6 +102,7 @@ class SendViewTestSetup(
|
|||
zecSend = zecSend,
|
||||
onZecSendChange = setZecSend,
|
||||
onBack = onBackAction,
|
||||
onSettings = { onSettingsCount.incrementAndGet() },
|
||||
onCreateAndSend = {
|
||||
onCreateCount.incrementAndGet()
|
||||
lastZecSend = it
|
||||
|
|
|
@ -57,7 +57,8 @@ class SendViewIntegrationTest {
|
|||
spendingKey = spendingKey,
|
||||
goToQrScanner = {},
|
||||
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.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.performClick
|
||||
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.ui.R
|
||||
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.assertOnConfirmation
|
||||
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.clickCreateAndSend
|
||||
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.SendStage
|
||||
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.setValidMemo
|
||||
import co.electriccoin.zcash.ui.test.getStringResource
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
@ -74,7 +77,7 @@ class SendViewTest : UiTestPrerequisites() {
|
|||
@Suppress("UNUSED_VARIABLE")
|
||||
val testSetup = newTestSetup()
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.send_create), ignoreCase = true).also {
|
||||
composeTestRule.onNodeWithTag(SendTag.SEND_FORM_BUTTON).also {
|
||||
it.assertExists()
|
||||
it.assertIsNotEnabled()
|
||||
}
|
||||
|
@ -82,7 +85,6 @@ class SendViewTest : UiTestPrerequisites() {
|
|||
|
||||
@Test
|
||||
@MediumTest
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun create_request_no_memo() =
|
||||
runTest {
|
||||
val testSetup = newTestSetup()
|
||||
|
@ -117,7 +119,6 @@ class SendViewTest : UiTestPrerequisites() {
|
|||
|
||||
@Test
|
||||
@MediumTest
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun create_request_with_memo() =
|
||||
runTest {
|
||||
val testSetup = newTestSetup()
|
||||
|
@ -154,7 +155,6 @@ class SendViewTest : UiTestPrerequisites() {
|
|||
|
||||
@Test
|
||||
@MediumTest
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun check_regex_functionality_valid_inputs() =
|
||||
runTest {
|
||||
val testSetup = newTestSetup()
|
||||
|
@ -213,7 +213,6 @@ class SendViewTest : UiTestPrerequisites() {
|
|||
|
||||
@Test
|
||||
@MediumTest
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun check_regex_functionality_invalid_inputs() =
|
||||
runTest {
|
||||
val testSetup = newTestSetup()
|
||||
|
@ -252,7 +251,6 @@ class SendViewTest : UiTestPrerequisites() {
|
|||
|
||||
@Test
|
||||
@MediumTest
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun max_memo_length() =
|
||||
runTest {
|
||||
val testSetup = newTestSetup()
|
||||
|
@ -294,14 +292,14 @@ class SendViewTest : UiTestPrerequisites() {
|
|||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun back_on_form() {
|
||||
fun on_settings_click_test() {
|
||||
val testSetup = newTestSetup()
|
||||
|
||||
assertEquals(0, testSetup.getOnBackCount())
|
||||
assertEquals(0, testSetup.getOnSettingsCount())
|
||||
|
||||
composeTestRule.clickBack()
|
||||
composeTestRule.clickSettingsTopAppBarMenu()
|
||||
|
||||
assertEquals(1, testSetup.getOnBackCount())
|
||||
assertEquals(1, testSetup.getOnSettingsCount())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -405,7 +403,7 @@ class SendViewTest : UiTestPrerequisites() {
|
|||
assertEquals(0, testSetup.getOnBackCount())
|
||||
|
||||
composeTestRule.assertOnSendFailure()
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.send_failure_button), ignoreCase = true).also {
|
||||
composeTestRule.onNodeWithTag(SEND_FAILED_BUTTON).also {
|
||||
it.assertExists()
|
||||
it.performClick()
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package co.electriccoin.zcash.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.widget.Toast
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
@ -9,7 +8,6 @@ import androidx.navigation.NavOptionsBuilder
|
|||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
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_MEMO
|
||||
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.HISTORY
|
||||
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.SCAN
|
||||
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.SUPPORT
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.WALLET_ADDRESS_DETAILS
|
||||
import co.electriccoin.zcash.ui.configuration.ConfigurationEntries
|
||||
import co.electriccoin.zcash.ui.configuration.RemoteConfig
|
||||
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.exportdata.WrapExportPrivateData
|
||||
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.scan.WrapScanValidator
|
||||
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.settings.WrapSettings
|
||||
import co.electriccoin.zcash.ui.screen.support.WrapSupport
|
||||
|
@ -48,19 +42,31 @@ internal fun MainActivity.Navigation() {
|
|||
val context = LocalContext.current
|
||||
val navController =
|
||||
rememberNavController().also {
|
||||
// This suppress is necessary, as this is how we set up the nav controller for tests.
|
||||
@SuppressLint("RestrictedApi")
|
||||
navControllerForTesting = it
|
||||
}
|
||||
|
||||
NavHost(navController = navController, startDestination = HOME) {
|
||||
composable(HOME) {
|
||||
WrapAccount(
|
||||
composable(HOME) { backStackEntry ->
|
||||
WrapHome(
|
||||
onPageChange = {
|
||||
homeViewModel.screenIndex.value = it
|
||||
},
|
||||
goAddressDetails = { navController.navigateJustOnce(WALLET_ADDRESS_DETAILS) },
|
||||
goBack = { finish() },
|
||||
goHistory = { navController.navigateJustOnce(HISTORY) },
|
||||
goReceive = { navController.navigateJustOnce(RECEIVE) },
|
||||
goSend = { navController.navigateJustOnce(SEND) },
|
||||
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)) {
|
||||
WrapCheckForUpdate()
|
||||
|
@ -112,33 +118,9 @@ internal fun MainActivity.Navigation() {
|
|||
}
|
||||
)
|
||||
}
|
||||
composable(RECEIVE) {
|
||||
WrapReceive(
|
||||
onBack = { navController.popBackStackJustOnce(RECEIVE) },
|
||||
onAddressDetails = { navController.navigateJustOnce(WALLET_ADDRESS_DETAILS) }
|
||||
)
|
||||
}
|
||||
composable(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) {
|
||||
// Pop back stack won't be right if we deep link into support
|
||||
WrapSupport(goBack = { navController.popBackStackJustOnce(SUPPORT) })
|
||||
|
@ -208,6 +190,7 @@ object NavigationArguments {
|
|||
|
||||
object NavigationTargets {
|
||||
const val ABOUT = "about"
|
||||
const val ACCOUNT = "account"
|
||||
const val EXPORT_PRIVATE_DATA = "export_private_data"
|
||||
const val HISTORY = "history"
|
||||
const val HOME = "home"
|
||||
|
|
|
@ -5,4 +5,5 @@ package co.electriccoin.zcash.ui.common.test
|
|||
*/
|
||||
object CommonTag {
|
||||
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.preference.StandardPreferenceKeys
|
||||
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.StateFlow
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
|
@ -15,8 +17,14 @@ import kotlinx.coroutines.flow.flow
|
|||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
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
|
||||
* Current Home sub-screen index in flow.
|
||||
*/
|
||||
val isBackgroundSyncEnabled: StateFlow<Boolean?> =
|
||||
flow {
|
||||
|
|
|
@ -57,7 +57,6 @@ import kotlinx.coroutines.sync.Mutex
|
|||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
import kotlin.time.ExperimentalTime
|
||||
|
||||
// To make this more multiplatform compatible, we need to remove the dependency on Context
|
||||
// for loading the preferences.
|
||||
|
@ -151,7 +150,7 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
|
|||
null
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class, ExperimentalTime::class)
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val walletSnapshot: StateFlow<WalletSnapshot?> =
|
||||
synchronizer
|
||||
.flatMapLatest {
|
||||
|
|
|
@ -7,5 +7,4 @@ object AccountTag {
|
|||
const val STATUS_VIEWS = "status_views"
|
||||
const val SINGLE_LINE_TEXT = "single_line_text"
|
||||
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.compose.runtime.Composable
|
||||
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.WalletViewModel
|
||||
import co.electriccoin.zcash.ui.configuration.ConfigurationEntries
|
||||
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.settings.viewmodel.SettingsViewModel
|
||||
import co.electriccoin.zcash.ui.screen.update.AppUpdateCheckerImp
|
||||
import co.electriccoin.zcash.ui.screen.update.model.UpdateState
|
||||
|
||||
@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(
|
||||
activity: ComponentActivity,
|
||||
goSettings: () -> Unit,
|
||||
goReceive: () -> Unit,
|
||||
goSend: () -> 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> {
|
||||
CheckUpdateViewModel.CheckUpdateViewModelFactory(
|
||||
activity.application,
|
||||
|
@ -63,7 +43,8 @@ internal fun WrapAccount(
|
|||
val isFiatConversionEnabled = ConfigurationEntries.IS_FIAT_CONVERSION_ENABLED.getValue(RemoteConfig.current)
|
||||
|
||||
if (null == walletSnapshot) {
|
||||
// Display loading indicator
|
||||
// Improve this by allowing screen composition and updating it after the data is available
|
||||
CircularScreenProgressIndicator()
|
||||
} else {
|
||||
Account(
|
||||
walletSnapshot = walletSnapshot,
|
||||
|
@ -71,11 +52,10 @@ internal fun WrapAccount(
|
|||
isKeepScreenOnDuringSync = isKeepScreenOnWhileSyncing,
|
||||
isFiatConversionEnabled = isFiatConversionEnabled,
|
||||
goSettings = goSettings,
|
||||
goReceive = goReceive,
|
||||
goSend = goSend,
|
||||
goHistory = goHistory
|
||||
)
|
||||
|
||||
// For benchmarking purposes
|
||||
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.common.DisableScreenTimeout
|
||||
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.component.Body
|
||||
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.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.fixture.WalletSnapshotFixture
|
||||
import co.electriccoin.zcash.ui.screen.account.AccountTag
|
||||
|
@ -48,8 +48,6 @@ private fun ComposablePreview() {
|
|||
isKeepScreenOnDuringSync = false,
|
||||
isFiatConversionEnabled = false,
|
||||
goSettings = {},
|
||||
goReceive = {},
|
||||
goSend = {},
|
||||
goHistory = {}
|
||||
)
|
||||
}
|
||||
|
@ -64,8 +62,6 @@ fun Account(
|
|||
isKeepScreenOnDuringSync: Boolean?,
|
||||
isFiatConversionEnabled: Boolean,
|
||||
goSettings: () -> Unit,
|
||||
goReceive: () -> Unit,
|
||||
goSend: () -> Unit,
|
||||
goHistory: () -> Unit
|
||||
) {
|
||||
Scaffold(topBar = {
|
||||
|
@ -76,8 +72,6 @@ fun Account(
|
|||
isUpdateAvailable = isUpdateAvailable,
|
||||
isKeepScreenOnDuringSync = isKeepScreenOnDuringSync,
|
||||
isFiatConversionEnabled = isFiatConversionEnabled,
|
||||
goReceive = goReceive,
|
||||
goSend = goSend,
|
||||
goHistory = goHistory,
|
||||
modifier =
|
||||
Modifier.padding(
|
||||
|
@ -97,11 +91,11 @@ private fun AccountTopAppBar(onSettings: () -> Unit) {
|
|||
hamburgerMenuActions = {
|
||||
IconButton(
|
||||
onClick = onSettings,
|
||||
modifier = Modifier.testTag(AccountTag.SETTINGS_TOP_BAR_BUTTON)
|
||||
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.account_menu_content_description)
|
||||
contentDescription = stringResource(id = R.string.settings_menu_content_description)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -115,8 +109,6 @@ private fun AccountMainContent(
|
|||
isUpdateAvailable: Boolean,
|
||||
isKeepScreenOnDuringSync: Boolean?,
|
||||
isFiatConversionEnabled: Boolean,
|
||||
goReceive: () -> Unit,
|
||||
goSend: () -> Unit,
|
||||
goHistory: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
|
@ -142,21 +134,7 @@ private fun AccountMainContent(
|
|||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
||||
|
||||
PrimaryButton(
|
||||
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))
|
||||
PrimaryButton(onClick = goHistory, text = stringResource(R.string.account_button_history))
|
||||
|
||||
if (isKeepScreenOnDuringSync == true && walletSnapshot.status == Synchronizer.Status.SYNCING) {
|
||||
DisableScreenTimeout()
|
||||
|
|
|
@ -10,6 +10,7 @@ import co.electriccoin.zcash.spackle.ClipboardManagerUtil
|
|||
import co.electriccoin.zcash.ui.MainActivity
|
||||
import co.electriccoin.zcash.ui.R
|
||||
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
|
||||
|
||||
@Composable
|
||||
|
@ -27,7 +28,8 @@ private fun WrapWalletAddresses(
|
|||
val walletAddresses = walletViewModel.addresses.collectAsStateWithLifecycle().value
|
||||
|
||||
if (null == walletAddresses) {
|
||||
// Display loading indicator
|
||||
// Improve this by allowing screen composition and updating it after the data is available
|
||||
CircularScreenProgressIndicator()
|
||||
} else {
|
||||
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.common.model.VersionInfo
|
||||
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.view.ExportPrivateData
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
|
@ -45,7 +46,8 @@ internal fun WrapExportPrivateData(
|
|||
val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value
|
||||
|
||||
if (synchronizer == null) {
|
||||
// Display loading indicator
|
||||
// Improve this by allowing screen composition and updating it after the data is available
|
||||
CircularScreenProgressIndicator()
|
||||
} else {
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
|
|
@ -3,79 +3,162 @@
|
|||
package co.electriccoin.zcash.ui.screen.home
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.viewModels
|
||||
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.common.viewmodel.CheckUpdateViewModel
|
||||
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
||||
import co.electriccoin.zcash.ui.configuration.ConfigurationEntries
|
||||
import co.electriccoin.zcash.ui.configuration.RemoteConfig
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.viewmodel.HomeViewModel
|
||||
import co.electriccoin.zcash.ui.screen.account.WrapAccount
|
||||
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.settings.viewmodel.SettingsViewModel
|
||||
import co.electriccoin.zcash.ui.screen.update.AppUpdateCheckerImp
|
||||
import co.electriccoin.zcash.ui.screen.update.model.UpdateState
|
||||
import co.electriccoin.zcash.ui.screen.receive.WrapReceive
|
||||
import co.electriccoin.zcash.ui.screen.send.WrapSend
|
||||
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")
|
||||
@Composable
|
||||
internal fun MainActivity.WrapHome(
|
||||
onPageChange: (HomeScreenIndex) -> Unit,
|
||||
goAddressDetails: () -> Unit,
|
||||
goBack: () -> Unit,
|
||||
goHistory: () -> Unit,
|
||||
goSettings: () -> Unit,
|
||||
goReceive: () -> Unit,
|
||||
goSend: () -> Unit,
|
||||
goHistory: () -> Unit
|
||||
goScan: () -> Unit,
|
||||
sendArgumentsWrapper: SendArgumentsWrapper
|
||||
) {
|
||||
WrapHome(
|
||||
this,
|
||||
goSettings = goSettings,
|
||||
goReceive = goReceive,
|
||||
goSend = goSend,
|
||||
onPageChange = onPageChange,
|
||||
goAddressDetails = goAddressDetails,
|
||||
goBack = goBack,
|
||||
goHistory = goHistory,
|
||||
goScan = goScan,
|
||||
goSettings = goSettings,
|
||||
sendArgumentsWrapper = sendArgumentsWrapper
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList", "LongMethod")
|
||||
@Composable
|
||||
@Suppress("LongParameterList")
|
||||
internal fun WrapHome(
|
||||
activity: ComponentActivity,
|
||||
goSettings: () -> Unit,
|
||||
goReceive: () -> Unit,
|
||||
goSend: () -> Unit,
|
||||
goAddressDetails: () -> Unit,
|
||||
goBack: () -> Unit,
|
||||
goHistory: () -> Unit,
|
||||
goSettings: () -> Unit,
|
||||
goScan: () -> Unit,
|
||||
onPageChange: (HomeScreenIndex) -> Unit,
|
||||
sendArgumentsWrapper: SendArgumentsWrapper
|
||||
) {
|
||||
// we want to show information about app update, if available
|
||||
val checkUpdateViewModel by activity.viewModels<CheckUpdateViewModel> {
|
||||
CheckUpdateViewModel.CheckUpdateViewModelFactory(
|
||||
activity.application,
|
||||
AppUpdateCheckerImp.new()
|
||||
val homeViewModel by activity.viewModels<HomeViewModel>()
|
||||
|
||||
// Flow for propagating the new page index to the pager in the view layer
|
||||
val forceHomePageIndexFlow: MutableSharedFlow<ForcePage?> =
|
||||
MutableSharedFlow(
|
||||
Int.MAX_VALUE,
|
||||
Int.MAX_VALUE,
|
||||
BufferOverflow.SUSPEND
|
||||
)
|
||||
}
|
||||
val updateAvailable =
|
||||
checkUpdateViewModel.updateInfo.collectAsStateWithLifecycle().value.let {
|
||||
it?.appUpdateInfo != null && it.state == UpdateState.Prepared
|
||||
val forceIndex = forceHomePageIndexFlow.collectAsState(initial = null).value
|
||||
|
||||
val homeGoBack: () -> Unit = {
|
||||
when (homeViewModel.screenIndex.value) {
|
||||
HomeScreenIndex.ACCOUNT -> goBack()
|
||||
HomeScreenIndex.SEND,
|
||||
HomeScreenIndex.RECEIVE,
|
||||
HomeScreenIndex.BALANCES -> forceHomePageIndexFlow.tryEmit(ForcePage())
|
||||
}
|
||||
}
|
||||
|
||||
val walletViewModel by activity.viewModels<WalletViewModel>()
|
||||
val walletSnapshot = walletViewModel.walletSnapshot.collectAsStateWithLifecycle().value
|
||||
BackHandler {
|
||||
homeGoBack()
|
||||
}
|
||||
|
||||
val settingsViewModel by activity.viewModels<SettingsViewModel>()
|
||||
|
||||
val isKeepScreenOnWhileSyncing = settingsViewModel.isKeepScreenOnWhileSyncing.collectAsStateWithLifecycle().value
|
||||
val isFiatConversionEnabled = ConfigurationEntries.IS_FIAT_CONVERSION_ENABLED.getValue(RemoteConfig.current)
|
||||
|
||||
if (null == walletSnapshot) {
|
||||
// Display loading indicator
|
||||
} else {
|
||||
Home(
|
||||
walletSnapshot = walletSnapshot,
|
||||
isUpdateAvailable = updateAvailable,
|
||||
isKeepScreenOnDuringSync = isKeepScreenOnWhileSyncing,
|
||||
isFiatConversionEnabled = isFiatConversionEnabled,
|
||||
goSettings = goSettings,
|
||||
goReceive = goReceive,
|
||||
goSend = goSend,
|
||||
goHistory = goHistory
|
||||
val tabs =
|
||||
persistentListOf(
|
||||
TabItem(
|
||||
index = HomeScreenIndex.ACCOUNT,
|
||||
title = stringResource(id = R.string.home_tab_account),
|
||||
testTag = HomeTag.TAB_ACCOUNT,
|
||||
screenContent = {
|
||||
WrapAccount(
|
||||
activity = activity,
|
||||
goHistory = goHistory,
|
||||
goSettings = goSettings,
|
||||
)
|
||||
}
|
||||
),
|
||||
TabItem(
|
||||
index = HomeScreenIndex.SEND,
|
||||
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.
|
||||
*/
|
||||
object HomeTag {
|
||||
const val STATUS_VIEWS = "status_views"
|
||||
const val SINGLE_LINE_TEXT = "single_line_text"
|
||||
const val FIAT_CONVERSION = "fiat_conversion"
|
||||
const val SETTINGS_TOP_BAR_BUTTON = "settings_top_bar_button"
|
||||
const val TAB_ACCOUNT = "tab_account"
|
||||
const val TAB_SEND = "tab_send"
|
||||
const val TAB_RECEIVE = "tab_receive"
|
||||
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
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.PageSize
|
||||
import androidx.compose.foundation.pager.PagerDefaults
|
||||
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.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.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.platform.LocalContext
|
||||
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 cash.z.ecc.android.sdk.Synchronizer
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrencyConversionRateState
|
||||
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 androidx.compose.ui.unit.dp
|
||||
import co.electriccoin.zcash.spackle.Twig
|
||||
import co.electriccoin.zcash.ui.design.component.GradientSurface
|
||||
import co.electriccoin.zcash.ui.design.component.HeaderWithZecIcon
|
||||
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.component.NavigationTabText
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.fixture.WalletSnapshotFixture
|
||||
import co.electriccoin.zcash.ui.screen.home.HomeTag
|
||||
import co.electriccoin.zcash.ui.screen.home.model.WalletDisplayValues
|
||||
import co.electriccoin.zcash.ui.screen.home.ForcePage
|
||||
import co.electriccoin.zcash.ui.screen.home.HomeScreenIndex
|
||||
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")
|
||||
@Composable
|
||||
|
@ -45,184 +42,115 @@ private fun ComposablePreview() {
|
|||
ZcashTheme(forceDarkMode = false) {
|
||||
GradientSurface {
|
||||
Home(
|
||||
walletSnapshot = WalletSnapshotFixture.new(),
|
||||
isUpdateAvailable = false,
|
||||
isKeepScreenOnDuringSync = false,
|
||||
isFiatConversionEnabled = false,
|
||||
goSettings = {},
|
||||
goReceive = {},
|
||||
goSend = {},
|
||||
goHistory = {}
|
||||
subScreens = persistentListOf(),
|
||||
forcePage = null,
|
||||
onPageChange = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun Home(
|
||||
walletSnapshot: WalletSnapshot,
|
||||
isUpdateAvailable: Boolean,
|
||||
isKeepScreenOnDuringSync: Boolean?,
|
||||
isFiatConversionEnabled: Boolean,
|
||||
goSettings: () -> Unit,
|
||||
goReceive: () -> Unit,
|
||||
goSend: () -> Unit,
|
||||
goHistory: () -> Unit
|
||||
subScreens: ImmutableList<TabItem>,
|
||||
forcePage: ForcePage?,
|
||||
onPageChange: (HomeScreenIndex) -> Unit,
|
||||
) {
|
||||
Scaffold(topBar = {
|
||||
HomeTopAppBar(onSettings = goSettings)
|
||||
}) { paddingValues ->
|
||||
HomeMainContent(
|
||||
walletSnapshot = walletSnapshot,
|
||||
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
|
||||
)
|
||||
val pagerState =
|
||||
rememberPagerState(
|
||||
initialPage = 0,
|
||||
initialPageOffsetFraction = 0f,
|
||||
pageCount = { subScreens.size }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HomeTopAppBar(onSettings: () -> Unit) {
|
||||
SmallTopAppBar(
|
||||
showTitleLogo = true,
|
||||
hamburgerMenuActions = {
|
||||
IconButton(
|
||||
onClick = onSettings,
|
||||
modifier = Modifier.testTag(HomeTag.SETTINGS_TOP_BAR_BUTTON)
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = co.electriccoin.zcash.ui.design.R.drawable.hamburger_menu_icon),
|
||||
contentDescription = stringResource(id = R.string.home_menu_content_description)
|
||||
)
|
||||
// Listening for the current page change
|
||||
LaunchedEffect(pagerState) {
|
||||
snapshotFlow {
|
||||
pagerState.currentPage
|
||||
}.distinctUntilChanged()
|
||||
.collect { page ->
|
||||
Twig.info { "Current pager page: $page" }
|
||||
onPageChange(HomeScreenIndex.fromIndex(page))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
private fun HomeMainContent(
|
||||
walletSnapshot: WalletSnapshot,
|
||||
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()
|
||||
// Force page change e.g. when system back navigation event detected
|
||||
forcePage?.let {
|
||||
LaunchedEffect(forcePage) {
|
||||
pagerState.animateScrollToPage(forcePage.currentPage.ordinal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Suppress("LongMethod", "MagicNumber")
|
||||
private fun Status(
|
||||
walletSnapshot: WalletSnapshot,
|
||||
updateAvailable: Boolean,
|
||||
isFiatConversionEnabled: Boolean
|
||||
) {
|
||||
val walletDisplayValues =
|
||||
WalletDisplayValues.getNextValues(
|
||||
LocalContext.current,
|
||||
walletSnapshot,
|
||||
updateAvailable
|
||||
)
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
Column(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.testTag(HomeTag.STATUS_VIEWS),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
|
||||
|
||||
if (walletDisplayValues.zecAmountText.isNotEmpty()) {
|
||||
HeaderWithZecIcon(amount = walletDisplayValues.zecAmountText)
|
||||
}
|
||||
|
||||
if (isFiatConversionEnabled) {
|
||||
Column(Modifier.testTag(HomeTag.FIAT_CONVERSION)) {
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
||||
|
||||
when (walletDisplayValues.fiatCurrencyAmountState) {
|
||||
is FiatCurrencyConversionRateState.Current -> {
|
||||
BodyWithFiatCurrencySymbol(
|
||||
amount = walletDisplayValues.fiatCurrencyAmountText
|
||||
Scaffold(
|
||||
bottomBar = {
|
||||
Column {
|
||||
Divider(
|
||||
thickness = DividerDefaults.Thickness,
|
||||
color = ZcashTheme.colors.dividerColor
|
||||
)
|
||||
TabRow(
|
||||
selectedTabIndex = pagerState.currentPage,
|
||||
// Don't use the predefined divider, as it's fixed position is below the tabs bar
|
||||
divider = {},
|
||||
indicator = { tabPositions ->
|
||||
TabRowDefaults.Indicator(
|
||||
modifier =
|
||||
Modifier
|
||||
.tabIndicatorOffset(tabPositions[pagerState.currentPage])
|
||||
.padding(horizontal = ZcashTheme.dimens.spacingDefault),
|
||||
color = ZcashTheme.colors.complementaryColor
|
||||
)
|
||||
}
|
||||
is FiatCurrencyConversionRateState.Stale -> {
|
||||
// Note: we should show information about staleness too
|
||||
BodyWithFiatCurrencySymbol(
|
||||
amount = walletDisplayValues.fiatCurrencyAmountText
|
||||
},
|
||||
modifier =
|
||||
Modifier
|
||||
.navigationBarsPadding()
|
||||
.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
|
||||
|
||||
if (walletDisplayValues.statusText.isNotEmpty()) {
|
||||
Body(
|
||||
text = walletDisplayValues.statusText,
|
||||
modifier = Modifier.testTag(HomeTag.SINGLE_LINE_TEXT)
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
pageSpacing = 0.dp,
|
||||
pageSize = PageSize.Fill,
|
||||
pageNestedScrollConnection =
|
||||
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.lifecycle.compose.collectAsStateWithLifecycle
|
||||
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.design.component.CircularScreenProgressIndicator
|
||||
import co.electriccoin.zcash.ui.screen.receive.view.Receive
|
||||
|
||||
@Composable
|
||||
@Suppress("LongParameterList")
|
||||
internal fun MainActivity.WrapReceive(
|
||||
onBack: () -> Unit,
|
||||
onAddressDetails: () -> Unit,
|
||||
) {
|
||||
WrapReceive(
|
||||
this,
|
||||
onBack = onBack,
|
||||
onAddressDetails = onAddressDetails,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Suppress("LongParameterList")
|
||||
internal fun WrapReceive(
|
||||
activity: ComponentActivity,
|
||||
onBack: () -> Unit,
|
||||
onSettings: () -> Unit,
|
||||
onAddressDetails: () -> Unit,
|
||||
) {
|
||||
val viewModel by activity.viewModels<WalletViewModel>()
|
||||
|
@ -36,24 +22,24 @@ internal fun WrapReceive(
|
|||
|
||||
WrapReceive(
|
||||
walletAddresses,
|
||||
onBack = onBack,
|
||||
onSettings = onSettings,
|
||||
onAddressDetails = onAddressDetails,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Suppress("LongParameterList")
|
||||
internal fun WrapReceive(
|
||||
walletAddresses: WalletAddresses?,
|
||||
onBack: () -> Unit,
|
||||
onSettings: () -> Unit,
|
||||
onAddressDetails: () -> Unit,
|
||||
) {
|
||||
if (null == walletAddresses) {
|
||||
// Display loading indicator
|
||||
// Improve this by allowing screen composition and updating it after the data is available
|
||||
CircularScreenProgressIndicator()
|
||||
} else {
|
||||
Receive(
|
||||
walletAddresses.unified,
|
||||
onBack = onBack,
|
||||
onSettings = onSettings,
|
||||
onAddressDetails = onAddressDetails,
|
||||
onAdjustBrightness = { /* Just for testing */ }
|
||||
)
|
||||
|
|
|
@ -22,6 +22,8 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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.text.style.TextOverflow
|
||||
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.common.BrightenScreen
|
||||
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.component.Body
|
||||
import co.electriccoin.zcash.ui.design.component.GradientSurface
|
||||
|
@ -50,7 +53,7 @@ private fun ComposablePreview() {
|
|||
GradientSurface {
|
||||
Receive(
|
||||
walletAddress = runBlocking { WalletAddressFixture.unified() },
|
||||
onBack = {},
|
||||
onSettings = {},
|
||||
onAddressDetails = {},
|
||||
onAdjustBrightness = {},
|
||||
)
|
||||
|
@ -61,7 +64,7 @@ private fun ComposablePreview() {
|
|||
@Composable
|
||||
fun Receive(
|
||||
walletAddress: WalletAddress,
|
||||
onBack: () -> Unit,
|
||||
onSettings: () -> Unit,
|
||||
onAddressDetails: () -> Unit,
|
||||
onAdjustBrightness: (Boolean) -> Unit,
|
||||
) {
|
||||
|
@ -71,7 +74,7 @@ fun Receive(
|
|||
Column {
|
||||
ReceiveTopAppBar(
|
||||
adjustBrightness = brightness,
|
||||
onBack = onBack,
|
||||
onSettings = onSettings,
|
||||
onBrightness = {
|
||||
onAdjustBrightness(!brightness)
|
||||
setBrightness(!brightness)
|
||||
|
@ -97,14 +100,11 @@ fun Receive(
|
|||
@Composable
|
||||
private fun ReceiveTopAppBar(
|
||||
adjustBrightness: Boolean,
|
||||
onBack: () -> Unit,
|
||||
onSettings: () -> Unit,
|
||||
onBrightness: () -> Unit
|
||||
) {
|
||||
SmallTopAppBar(
|
||||
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 = {
|
||||
IconButton(
|
||||
onClick = onBrightness
|
||||
|
@ -119,6 +119,17 @@ private fun ReceiveTopAppBar(
|
|||
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.R
|
||||
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 kotlinx.coroutines.runBlocking
|
||||
|
||||
|
@ -29,7 +30,8 @@ private fun WrapRequest(
|
|||
val walletAddresses = walletViewModel.addresses.collectAsStateWithLifecycle().value
|
||||
|
||||
if (null == walletAddresses) {
|
||||
// Display loading indicator
|
||||
// Improve this by allowing screen composition and updating it after the data is available
|
||||
CircularScreenProgressIndicator()
|
||||
} else {
|
||||
Request(
|
||||
walletAddresses.unified,
|
||||
|
|
|
@ -10,6 +10,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import co.electriccoin.zcash.ui.MainActivity
|
||||
import co.electriccoin.zcash.ui.R
|
||||
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.view.Scan
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -40,7 +41,8 @@ fun WrapScan(
|
|||
val scope = rememberCoroutineScope()
|
||||
|
||||
if (synchronizer == null) {
|
||||
// Display loading indicator
|
||||
// Improve this by allowing screen composition and updating it after the data is available
|
||||
CircularScreenProgressIndicator()
|
||||
} else {
|
||||
Scan(
|
||||
snackbarHostState = snackbarHostState,
|
||||
|
|
|
@ -9,6 +9,7 @@ import co.electriccoin.zcash.ui.MainActivity
|
|||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.viewmodel.SecretState
|
||||
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
|
||||
|
||||
@Composable
|
||||
|
@ -39,7 +40,8 @@ private fun WrapSeedRecovery(
|
|||
val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value
|
||||
|
||||
if (null == synchronizer || null == persistableWallet) {
|
||||
// Display loading indicator
|
||||
// Improve this by allowing screen composition and updating it after the data is available
|
||||
CircularScreenProgressIndicator()
|
||||
} else {
|
||||
SeedRecovery(
|
||||
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.sdk.extension.send
|
||||
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.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.model.SendArgumentsWrapper
|
||||
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
|
||||
|
||||
@Composable
|
||||
internal fun MainActivity.WrapSend(
|
||||
sendArgumentsWrapper: SendArgumentsWrapper?,
|
||||
goToQrScanner: () -> Unit,
|
||||
goBack: () -> Unit
|
||||
) {
|
||||
WrapSend(this, sendArgumentsWrapper, goToQrScanner, goBack)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WrapSend(
|
||||
internal fun WrapSend(
|
||||
activity: ComponentActivity,
|
||||
sendArgumentsWrapper: SendArgumentsWrapper?,
|
||||
goToQrScanner: () -> Unit,
|
||||
goBack: () -> Unit
|
||||
goBack: () -> Unit,
|
||||
goSettings: () -> Unit,
|
||||
) {
|
||||
val hasCameraFeature = activity.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
|
||||
|
||||
|
@ -53,7 +45,16 @@ private fun WrapSend(
|
|||
|
||||
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")
|
||||
|
@ -66,6 +67,7 @@ internal fun WrapSend(
|
|||
spendingKey: UnifiedSpendingKey?,
|
||||
goToQrScanner: () -> Unit,
|
||||
goBack: () -> Unit,
|
||||
goSettings: () -> Unit,
|
||||
hasCameraFeature: Boolean
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
@ -83,11 +85,13 @@ internal fun WrapSend(
|
|||
when (sendStage) {
|
||||
SendStage.Form -> goBack()
|
||||
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.SendSuccessful -> goBack()
|
||||
SendStage.SendSuccessful -> {
|
||||
setZecSend(null)
|
||||
setSendStage(SendStage.Form)
|
||||
goBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,7 +100,8 @@ internal fun WrapSend(
|
|||
}
|
||||
|
||||
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 {
|
||||
Send(
|
||||
mySpendableBalance = spendableBalance,
|
||||
|
@ -106,6 +111,7 @@ internal fun WrapSend(
|
|||
zecSend = zecSend,
|
||||
onZecSendChange = setZecSend,
|
||||
onBack = onBackAction,
|
||||
onSettings = goSettings,
|
||||
onCreateAndSend = {
|
||||
scope.launch {
|
||||
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.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.outlined.QrCodeScanner
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
@ -30,6 +27,8 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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.text.input.KeyboardType
|
||||
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.ZatoshiFixture
|
||||
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.component.Body
|
||||
import co.electriccoin.zcash.ui.design.component.FormTextField
|
||||
import co.electriccoin.zcash.ui.design.component.GradientSurface
|
||||
import co.electriccoin.zcash.ui.design.component.Header
|
||||
import co.electriccoin.zcash.ui.design.component.PrimaryButton
|
||||
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
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.abbreviated
|
||||
import co.electriccoin.zcash.ui.screen.send.ext.valueOrEmptyChar
|
||||
|
@ -76,6 +78,7 @@ private fun PreviewSendForm() {
|
|||
onCreateAndSend = {},
|
||||
onQrScannerOpen = {},
|
||||
onBack = {},
|
||||
onSettings = {},
|
||||
hasCameraFeature = true
|
||||
)
|
||||
}
|
||||
|
@ -123,7 +126,7 @@ private fun PreviewSendFailure() {
|
|||
private fun PreviewSendConfirmation() {
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
GradientSurface {
|
||||
Confirmation(
|
||||
SendConfirmation(
|
||||
zecSend =
|
||||
ZecSend(
|
||||
destination = runBlocking { WalletAddressFixture.sapling() },
|
||||
|
@ -146,6 +149,7 @@ fun Send(
|
|||
zecSend: ZecSend?,
|
||||
onZecSendChange: (ZecSend) -> Unit,
|
||||
onBack: () -> Unit,
|
||||
onSettings: () -> Unit,
|
||||
onCreateAndSend: (ZecSend) -> Unit,
|
||||
onQrScannerOpen: () -> Unit,
|
||||
hasCameraFeature: Boolean
|
||||
|
@ -153,7 +157,8 @@ fun Send(
|
|||
Scaffold(topBar = {
|
||||
SendTopAppBar(
|
||||
onBack = onBack,
|
||||
showBackNavigationButton = sendStage != SendStage.Sending
|
||||
onSettings = onSettings,
|
||||
showBackNavigationButton = (sendStage != SendStage.Sending && sendStage != SendStage.Form)
|
||||
)
|
||||
}) { paddingValues ->
|
||||
SendMainContent(
|
||||
|
@ -184,28 +189,33 @@ fun Send(
|
|||
}
|
||||
|
||||
@Composable
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
private fun SendTopAppBar(
|
||||
onBack: () -> Unit,
|
||||
onSettings: () -> Unit,
|
||||
showBackNavigationButton: Boolean = true
|
||||
) {
|
||||
if (showBackNavigationButton) {
|
||||
TopAppBar(
|
||||
title = { Text(text = stringResource(id = R.string.send_title)) },
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
onClick = onBack
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowBack,
|
||||
contentDescription = stringResource(R.string.send_back_content_description)
|
||||
)
|
||||
}
|
||||
SmallTopAppBar(
|
||||
titleText = stringResource(id = R.string.send_title),
|
||||
onBack = onBack,
|
||||
backText =
|
||||
if (showBackNavigationButton) {
|
||||
stringResource(id = R.string.send_back)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
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")
|
||||
|
@ -239,7 +249,7 @@ private fun SendMainContent(
|
|||
)
|
||||
}
|
||||
(sendStage == SendStage.Confirmation) -> {
|
||||
Confirmation(
|
||||
SendConfirmation(
|
||||
zecSend = zecSend,
|
||||
onConfirmation = {
|
||||
onSendStageChange(SendStage.Sending)
|
||||
|
@ -426,13 +436,14 @@ private fun SendForm(
|
|||
// Check for ABBREVIATION_INDEX goes away once proper address validation is in place.
|
||||
// For now, it just prevents a crash on the confirmation screen.
|
||||
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
|
||||
private fun Confirmation(
|
||||
private fun SendConfirmation(
|
||||
zecSend: ZecSend,
|
||||
onConfirmation: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
|
@ -465,7 +476,10 @@ private fun Confirmation(
|
|||
)
|
||||
|
||||
PrimaryButton(
|
||||
modifier = Modifier.padding(top = dimens.spacingSmall),
|
||||
modifier =
|
||||
Modifier
|
||||
.padding(top = dimens.spacingSmall)
|
||||
.testTag(SendTag.SEND_CONFIRMATION_BUTTON),
|
||||
onClick = onConfirmation,
|
||||
text = stringResource(id = R.string.send_confirmation_button),
|
||||
outerPaddingValues = PaddingValues(top = dimens.spacingSmall)
|
||||
|
@ -566,7 +580,10 @@ private fun SendSuccessful(
|
|||
)
|
||||
|
||||
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),
|
||||
onClick = onDone,
|
||||
outerPaddingValues = PaddingValues(top = dimens.spacingSmall)
|
||||
|
@ -616,7 +633,10 @@ private fun SendFailure(
|
|||
)
|
||||
|
||||
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),
|
||||
onClick = onDone,
|
||||
outerPaddingValues = PaddingValues(top = dimens.spacingSmall)
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
package co.electriccoin.zcash.ui.screen.settings
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.runtime.Composable
|
||||
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.configuration.ConfigurationEntries
|
||||
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.view.Settings
|
||||
import co.electriccoin.zcash.ui.screen.settings.viewmodel.SettingsViewModel
|
||||
|
@ -60,13 +62,18 @@ private fun WrapSettings(
|
|||
val isKeepScreenOnWhileSyncing = settingsViewModel.isKeepScreenOnWhileSyncing.collectAsStateWithLifecycle().value
|
||||
val isAnalyticsEnabled = settingsViewModel.isAnalyticsEnabled.collectAsStateWithLifecycle().value
|
||||
|
||||
BackHandler {
|
||||
goBack()
|
||||
}
|
||||
|
||||
@Suppress("ComplexCondition")
|
||||
if (null == synchronizer ||
|
||||
null == isAnalyticsEnabled ||
|
||||
null == isBackgroundSyncEnabled ||
|
||||
null == isKeepScreenOnWhileSyncing
|
||||
) {
|
||||
// Display loading indicator
|
||||
// Improve this by allowing screen composition and updating it after the data is available
|
||||
CircularScreenProgressIndicator()
|
||||
} else {
|
||||
Settings(
|
||||
TroubleshootingParameters(
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
<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_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="zcash_logo_content_description">Zcash logo</string>
|
||||
<string name="not_implemented_yet">Not implemented yet.</string>
|
||||
<string name="settings_menu_content_description">Open Settings</string>
|
||||
</resources>
|
||||
|
|
|
@ -1,24 +1,8 @@
|
|||
<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_status_syncing_format" formatted="true">Syncing - <xliff:g id="synced_percent" example="50.25">
|
||||
%1$s</xliff:g>%%</string> <!-- double %% for escaping -->
|
||||
<string name="home_status_syncing_catchup">Syncing</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_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>
|
||||
<string name="home_tab_account">Account</string>
|
||||
<string name="home_tab_send">Send</string>
|
||||
<string name="home_tab_receive">Receive</string>
|
||||
<string name="home_tab_balances">Balances</string>
|
||||
</resources>
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<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_qr_code_content_description">QR code for 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">
|
||||
<string name="send_title">Send ZEC</string>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="send_title">Send</string>
|
||||
<string name="send_back">Back</string>
|
||||
<string name="send_back_content_description">Back</string>
|
||||
<string name="send_scan_content_description">Scan</string>
|
||||
<string name="send_balance" formatted="true"><xliff:g id="balance" example="12.345">%1$s</xliff:g> ZEC Available</string>
|
||||
|
|
|
@ -7,9 +7,10 @@ import android.os.Build
|
|||
import android.os.LocaleList
|
||||
import androidx.activity.viewModels
|
||||
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.junit4.AndroidComposeTestRule
|
||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||
import androidx.compose.ui.test.junit4.ComposeTestRule
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
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.design.component.ConfigurationOverride
|
||||
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.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.runBlocking
|
||||
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(
|
||||
uiMode: UiMode,
|
||||
locale: String,
|
||||
|
@ -265,17 +275,22 @@ class ScreenshotTest : UiTestPrerequisites() {
|
|||
return
|
||||
}
|
||||
|
||||
// These are the home screen bottom navigation sub-screens
|
||||
onboardingScreenshots(resContext, tag, composeTestRule)
|
||||
recoveryScreenshots(resContext, tag, composeTestRule)
|
||||
homeScreenshots(resContext, tag, composeTestRule)
|
||||
|
||||
// These are the buttons on the home screen
|
||||
navigateTo(NavigationTargets.SEND)
|
||||
composeTestRule.navigateInHomeTab(HomeTag.TAB_ACCOUNT)
|
||||
accountScreenshots(tag, composeTestRule)
|
||||
|
||||
composeTestRule.navigateInHomeTab(HomeTag.TAB_SEND)
|
||||
sendZecScreenshots(resContext, tag, composeTestRule)
|
||||
|
||||
navigateTo(NavigationTargets.RECEIVE)
|
||||
composeTestRule.navigateInHomeTab(HomeTag.TAB_RECEIVE)
|
||||
receiveZecScreenshots(resContext, tag, composeTestRule)
|
||||
|
||||
composeTestRule.navigateInHomeTab(HomeTag.TAB_BALANCES)
|
||||
balancesScreenshots(resContext, tag, composeTestRule)
|
||||
|
||||
navigateTo(NavigationTargets.WALLET_ADDRESS_DETAILS)
|
||||
addressDetailsScreenshots(resContext, tag, composeTestRule)
|
||||
|
||||
|
@ -283,7 +298,7 @@ class ScreenshotTest : UiTestPrerequisites() {
|
|||
transactionHistoryScreenshots(resContext, tag, composeTestRule)
|
||||
|
||||
navigateTo(NavigationTargets.SETTINGS)
|
||||
settingsScreenshots(tag, composeTestRule)
|
||||
settingsScreenshots(resContext, tag, composeTestRule)
|
||||
|
||||
// These are the Settings screen items
|
||||
// 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(
|
||||
resContext: Context,
|
||||
private fun accountScreenshots(
|
||||
tag: String,
|
||||
composeTestRule: AndroidComposeTestRule<ActivityScenarioRule<MainActivity>, MainActivity>
|
||||
) {
|
||||
|
@ -374,17 +388,32 @@ private fun homeScreenshots(
|
|||
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()
|
||||
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(
|
||||
resContext: Context,
|
||||
tag: String,
|
||||
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()
|
||||
}
|
||||
|
||||
|
@ -438,19 +467,16 @@ private fun receiveZecScreenshots(
|
|||
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()
|
||||
}
|
||||
|
||||
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(
|
||||
|
@ -495,7 +521,7 @@ private fun sendZecScreenshots(
|
|||
// Screenshot: Fulfilled form
|
||||
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()
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue