[#664] Transaction History List
* [#664] Transaction history * Move under the screens folder * Fix Request screen Preview * Add TODO link * Improve Text design component * HistoryView UI enhancing * Adopt ZcashCurrency API * Add transaction history sync state * Compact time format * Bump Compose Material Icons to v1.5.0-beta02 * Add support for pending and expired transactions * Add progress in syncing with no transaction yet * Screenshot test * Simplified no transaction check * Transaction history manual test case * Home screen history button test * Fix flow collecting * View tests * Sent transaction sign * Remove unused transaction snapshot from VM --------- Co-authored-by: Honza <rychnovsky.honza@gmail.com>
This commit is contained in:
parent
2b55b1df4f
commit
fc7321e049
|
@ -0,0 +1,18 @@
|
|||
# Check transactions appear in the Transaction History screen
|
||||
|
||||
## Test Prerequisites
|
||||
- Testnet wallet seed phrase mnemonic:
|
||||
`board palm case fever fuel above dinosaur caution erode search ignore damage print rare lady agent stereo tomorrow end thank hurry deputy swamp wild`
|
||||
- And its birthday height: `2379900`
|
||||
- Install the latest Testnet variant wallet app
|
||||
- Open the wallet app and for restoring its previous state, use the wallet information above
|
||||
|
||||
## Test
|
||||
- Click on the _Transaction History_ button on the _Home_ screen (an additional scroll down may be needed)
|
||||
- **Confirm** there are no transactions displayed. There should be only a text saying that the transaction may
|
||||
appear once the sync completes.
|
||||
- Go back to the _Home_ screen
|
||||
- **Wait** for a few minutes to sync completes and _Up-to-date_ is displayed under the current balance text
|
||||
- Go to the _Transaction History_ screen again
|
||||
- **Confirm** that there are some transactions displayed. There should be some incoming as well as outgoing
|
||||
transactions displayed.
|
|
@ -120,7 +120,7 @@ ANDROIDX_APPCOMPAT_VERSION=1.6.1
|
|||
ANDROIDX_CAMERA_VERSION=1.3.0-alpha06
|
||||
ANDROIDX_COMPOSE_COMPILER_VERSION=1.4.7
|
||||
ANDROIDX_COMPOSE_MATERIAL3_VERSION=1.1.0-rc01
|
||||
ANDROIDX_COMPOSE_MATERIAL_ICONS_VERSION=1.4.3
|
||||
ANDROIDX_COMPOSE_MATERIAL_ICONS_VERSION=1.5.0-beta02
|
||||
ANDROIDX_COMPOSE_VERSION=1.4.3
|
||||
ANDROIDX_CONSTRAINTLAYOUT_VERSION=1.0.1
|
||||
ANDROIDX_CORE_VERSION=1.9.0
|
||||
|
|
|
@ -10,6 +10,7 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import co.electriccoin.zcash.ui.design.R
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
|
||||
|
@ -30,15 +31,20 @@ fun Header(
|
|||
}
|
||||
|
||||
@Composable
|
||||
@Suppress("LongParameterList")
|
||||
fun Body(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
maxLines: Int = Int.MAX_VALUE,
|
||||
overflow: TextOverflow = TextOverflow.Clip,
|
||||
textAlign: TextAlign = TextAlign.Start,
|
||||
color: Color = MaterialTheme.colorScheme.onBackground,
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
color = color,
|
||||
maxLines = maxLines,
|
||||
overflow = overflow,
|
||||
textAlign = textAlign,
|
||||
modifier = modifier,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
|
|
|
@ -30,6 +30,7 @@ android {
|
|||
"src/main/res/ui/about",
|
||||
"src/main/res/ui/backup",
|
||||
"src/main/res/ui/common",
|
||||
"src/main/res/ui/history",
|
||||
"src/main/res/ui/home",
|
||||
"src/main/res/ui/onboarding",
|
||||
"src/main/res/ui/receive",
|
||||
|
|
|
@ -2,7 +2,6 @@ package co.electriccoin.zcash.ui.common
|
|||
|
||||
import androidx.test.filters.FlakyTest
|
||||
import androidx.test.filters.SmallTest
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
@ -17,7 +16,7 @@ import kotlin.time.TimeSource
|
|||
|
||||
class FlowExtTest {
|
||||
|
||||
@OptIn(ExperimentalTime::class, ExperimentalCoroutinesApi::class)
|
||||
@OptIn(ExperimentalTime::class)
|
||||
@Test
|
||||
@SmallTest
|
||||
fun throttle_one_sec() = runTest {
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
package co.electriccoin.zcash.ui.screen.history
|
||||
|
||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.screen.history.state.TransactionHistorySyncState
|
||||
import co.electriccoin.zcash.ui.screen.history.view.History
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
class HistoryTestSetup(
|
||||
private val composeTestRule: ComposeContentTestRule,
|
||||
initialHistorySyncState: TransactionHistorySyncState
|
||||
) {
|
||||
|
||||
private val onBackCount = AtomicInteger(0)
|
||||
|
||||
fun getOnBackCount(): Int {
|
||||
composeTestRule.waitForIdle()
|
||||
return onBackCount.get()
|
||||
}
|
||||
|
||||
init {
|
||||
composeTestRule.setContent {
|
||||
ZcashTheme {
|
||||
History(
|
||||
transactionState = initialHistorySyncState,
|
||||
goBack = {
|
||||
onBackCount.incrementAndGet()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package co.electriccoin.zcash.ui.screen.history.fixture
|
||||
|
||||
import cash.z.ecc.android.sdk.fixture.TransactionOverviewFixture
|
||||
import cash.z.ecc.android.sdk.model.TransactionOverview
|
||||
import co.electriccoin.zcash.ui.screen.history.state.TransactionHistorySyncState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal object TransactionHistorySyncStateFixture {
|
||||
val TRANSACTIONS = persistentListOf(
|
||||
TransactionOverviewFixture.new(id = 0),
|
||||
TransactionOverviewFixture.new(id = 1),
|
||||
TransactionOverviewFixture.new(id = 2)
|
||||
)
|
||||
val STATE = TransactionHistorySyncState.Syncing(TRANSACTIONS)
|
||||
|
||||
fun new(
|
||||
transactions: ImmutableList<TransactionOverview> = TRANSACTIONS,
|
||||
state: TransactionHistorySyncState = STATE
|
||||
) = when (state) {
|
||||
is TransactionHistorySyncState.Syncing -> {
|
||||
state.copy(transactions)
|
||||
}
|
||||
is TransactionHistorySyncState.Done -> {
|
||||
state.copy(transactions)
|
||||
}
|
||||
TransactionHistorySyncState.Loading -> {
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
package co.electriccoin.zcash.ui.screen.history.view
|
||||
|
||||
import androidx.compose.ui.test.assertHeightIsAtLeast
|
||||
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.unit.dp
|
||||
import androidx.test.filters.MediumTest
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.screen.history.HistoryTag
|
||||
import co.electriccoin.zcash.ui.screen.history.HistoryTestSetup
|
||||
import co.electriccoin.zcash.ui.screen.history.fixture.TransactionHistorySyncStateFixture
|
||||
import co.electriccoin.zcash.ui.screen.history.state.TransactionHistorySyncState
|
||||
import co.electriccoin.zcash.ui.test.getStringResource
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class HistoryViewTest {
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun check_loading_state() {
|
||||
newTestSetup(TransactionHistorySyncState.Loading)
|
||||
|
||||
composeTestRule.onNodeWithTag(HistoryTag.PROGRESS).also {
|
||||
it.assertExists()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun check_syncing_state() {
|
||||
newTestSetup(
|
||||
TransactionHistorySyncStateFixture.new(
|
||||
state = TransactionHistorySyncStateFixture.STATE,
|
||||
transactions = TransactionHistorySyncStateFixture.TRANSACTIONS
|
||||
)
|
||||
)
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.history_syncing)).also {
|
||||
it.assertExists()
|
||||
}
|
||||
// No progress bar, as we have some transactions laid out
|
||||
composeTestRule.onNodeWithTag(HistoryTag.PROGRESS).also {
|
||||
it.assertDoesNotExist()
|
||||
}
|
||||
composeTestRule.onNodeWithTag(HistoryTag.TRANSACTION_LIST).also {
|
||||
it.assertExists()
|
||||
it.assertHeightIsAtLeast(1.dp)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun check_done_state_no_transactions() {
|
||||
newTestSetup(
|
||||
TransactionHistorySyncStateFixture.new(
|
||||
state = TransactionHistorySyncState.Done(persistentListOf()),
|
||||
transactions = persistentListOf()
|
||||
)
|
||||
)
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.history_syncing)).also {
|
||||
it.assertDoesNotExist()
|
||||
}
|
||||
composeTestRule.onNodeWithTag(HistoryTag.PROGRESS).also {
|
||||
it.assertDoesNotExist()
|
||||
}
|
||||
composeTestRule.onNodeWithTag(HistoryTag.TRANSACTION_LIST).also {
|
||||
it.assertDoesNotExist()
|
||||
}
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.history_empty)).also {
|
||||
it.assertExists()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun check_done_state_with_transactions() {
|
||||
newTestSetup(
|
||||
TransactionHistorySyncStateFixture.new(
|
||||
state = TransactionHistorySyncState.Done(persistentListOf()),
|
||||
transactions = TransactionHistorySyncStateFixture.TRANSACTIONS
|
||||
)
|
||||
)
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.history_syncing)).also {
|
||||
it.assertDoesNotExist()
|
||||
}
|
||||
composeTestRule.onNodeWithTag(HistoryTag.PROGRESS).also {
|
||||
it.assertDoesNotExist()
|
||||
}
|
||||
composeTestRule.onNodeWithTag(HistoryTag.TRANSACTION_LIST).also {
|
||||
it.assertExists()
|
||||
it.assertHeightIsAtLeast(1.dp)
|
||||
}
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.history_empty)).also {
|
||||
it.assertDoesNotExist()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun back() {
|
||||
val testSetup = newTestSetup()
|
||||
|
||||
assertEquals(0, testSetup.getOnBackCount())
|
||||
|
||||
composeTestRule.onNodeWithContentDescription(
|
||||
getStringResource(R.string.history_back_content_description)
|
||||
).also {
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
assertEquals(1, testSetup.getOnBackCount())
|
||||
}
|
||||
|
||||
private fun newTestSetup(
|
||||
transactionHistorySyncState: TransactionHistorySyncState = TransactionHistorySyncStateFixture.new()
|
||||
) = HistoryTestSetup(
|
||||
composeTestRule = composeTestRule,
|
||||
initialHistorySyncState = transactionHistorySyncState
|
||||
)
|
||||
}
|
|
@ -5,7 +5,6 @@ import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
|||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.screen.home.model.WalletSnapshot
|
||||
import co.electriccoin.zcash.ui.screen.home.view.Home
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
class HomeTestSetup(
|
||||
|
@ -20,6 +19,7 @@ class HomeTestSetup(
|
|||
private val onSupportCount = AtomicInteger(0)
|
||||
private val onReceiveCount = AtomicInteger(0)
|
||||
private val onSendCount = AtomicInteger(0)
|
||||
private val onHistoryCount = AtomicInteger(0)
|
||||
|
||||
fun getOnAboutCount(): Int {
|
||||
composeTestRule.waitForIdle()
|
||||
|
@ -51,6 +51,11 @@ class HomeTestSetup(
|
|||
return onSendCount.get()
|
||||
}
|
||||
|
||||
fun getOnHistoryCount(): Int {
|
||||
composeTestRule.waitForIdle()
|
||||
return onHistoryCount.get()
|
||||
}
|
||||
|
||||
fun getWalletSnapshot(): WalletSnapshot {
|
||||
composeTestRule.waitForIdle()
|
||||
return walletSnapshot
|
||||
|
@ -62,7 +67,6 @@ class HomeTestSetup(
|
|||
val drawerValues = drawerBackHandler()
|
||||
Home(
|
||||
walletSnapshot,
|
||||
transactionHistory = persistentListOf(),
|
||||
isUpdateAvailable = false,
|
||||
isKeepScreenOnDuringSync = false,
|
||||
isFiatConversionEnabled = isShowFiatConversion,
|
||||
|
@ -86,6 +90,9 @@ class HomeTestSetup(
|
|||
goSend = {
|
||||
onSendCount.incrementAndGet()
|
||||
},
|
||||
goHistory = {
|
||||
onHistoryCount.incrementAndGet()
|
||||
},
|
||||
resetSdk = {},
|
||||
drawerState = drawerValues.drawerState,
|
||||
scope = drawerValues.scope
|
||||
|
|
|
@ -121,6 +121,18 @@ class HomeViewTest : UiTestPrerequisites() {
|
|||
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_seed() {
|
||||
|
@ -217,3 +229,10 @@ private fun ComposeContentTestRule.clickSend() {
|
|||
it.performClick()
|
||||
}
|
||||
}
|
||||
|
||||
private fun ComposeContentTestRule.clickHistory() {
|
||||
onNodeWithText(getStringResource(R.string.home_button_history)).also {
|
||||
it.performScrollTo()
|
||||
it.performClick()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ 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
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.ABOUT
|
||||
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
|
||||
|
@ -25,6 +26,7 @@ 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.address.WrapWalletAddresses
|
||||
import co.electriccoin.zcash.ui.screen.history.WrapHistory
|
||||
import co.electriccoin.zcash.ui.screen.home.WrapHome
|
||||
import co.electriccoin.zcash.ui.screen.receive.WrapReceive
|
||||
import co.electriccoin.zcash.ui.screen.request.WrapRequest
|
||||
|
@ -54,6 +56,7 @@ internal fun MainActivity.Navigation() {
|
|||
goAbout = { navController.navigateJustOnce(ABOUT) },
|
||||
goReceive = { navController.navigateJustOnce(RECEIVE) },
|
||||
goSend = { navController.navigateJustOnce(SEND) },
|
||||
goHistory = { navController.navigateJustOnce(HISTORY) }
|
||||
)
|
||||
|
||||
if (ConfigurationEntries.IS_APP_UPDATE_CHECK_ENABLED.getValue(RemoteConfig.current)) {
|
||||
|
@ -128,6 +131,10 @@ internal fun MainActivity.Navigation() {
|
|||
goBack = { navController.popBackStackJustOnce(SCAN) }
|
||||
)
|
||||
}
|
||||
|
||||
composable(HISTORY) {
|
||||
WrapHistory(goBack = { navController.navigateUp() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -178,6 +185,8 @@ object NavigationTargets {
|
|||
|
||||
const val REQUEST = "request"
|
||||
|
||||
const val HISTORY = "history"
|
||||
|
||||
const val SEND = "send"
|
||||
|
||||
const val SUPPORT = "support"
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
package co.electriccoin.zcash.ui.screen.history
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import cash.z.ecc.android.sdk.internal.Twig
|
||||
import co.electriccoin.zcash.ui.MainActivity
|
||||
import co.electriccoin.zcash.ui.screen.history.view.History
|
||||
import co.electriccoin.zcash.ui.screen.home.viewmodel.WalletViewModel
|
||||
|
||||
@Composable
|
||||
internal fun MainActivity.WrapHistory(
|
||||
goBack: () -> Unit
|
||||
) {
|
||||
WrapHistory(
|
||||
activity = this,
|
||||
goBack = goBack
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun WrapHistory(
|
||||
activity: ComponentActivity,
|
||||
goBack: () -> Unit
|
||||
) {
|
||||
val walletViewModel by activity.viewModels<WalletViewModel>()
|
||||
|
||||
val transactionHistoryState =
|
||||
walletViewModel.transactionHistoryState.collectAsStateWithLifecycle().value
|
||||
|
||||
Twig.info { "Current transaction history state: $transactionHistoryState" }
|
||||
|
||||
History(
|
||||
transactionState = transactionHistoryState,
|
||||
goBack = goBack
|
||||
)
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package co.electriccoin.zcash.ui.screen.history
|
||||
|
||||
/**
|
||||
* These are only used for automated testing.
|
||||
*/
|
||||
object HistoryTag {
|
||||
const val TRANSACTION_LIST = "transaction_list"
|
||||
const val PROGRESS = "progress_bar"
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package co.electriccoin.zcash.ui.screen.history.state
|
||||
|
||||
import cash.z.ecc.android.sdk.model.TransactionOverview
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
sealed class TransactionHistorySyncState {
|
||||
|
||||
object Loading : TransactionHistorySyncState() {
|
||||
override fun toString() = "Loading" // NON-NLS
|
||||
}
|
||||
data class Syncing(val transactions: ImmutableList<TransactionOverview>) : TransactionHistorySyncState() {
|
||||
fun hasNoTransactions(): Boolean {
|
||||
return transactions.isEmpty()
|
||||
}
|
||||
}
|
||||
data class Done(val transactions: ImmutableList<TransactionOverview>) : TransactionHistorySyncState() {
|
||||
fun hasNoTransactions(): Boolean {
|
||||
return transactions.isEmpty()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,300 @@
|
|||
package co.electriccoin.zcash.ui.screen.history.view
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Cancel
|
||||
import androidx.compose.material.icons.outlined.ArrowCircleDown
|
||||
import androidx.compose.material.icons.outlined.ArrowCircleUp
|
||||
import androidx.compose.material.icons.twotone.ArrowCircleDown
|
||||
import androidx.compose.material.icons.twotone.ArrowCircleUp
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
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.ui.Alignment
|
||||
import androidx.compose.ui.Alignment.Companion.Center
|
||||
import androidx.compose.ui.Alignment.Companion.TopCenter
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import cash.z.ecc.android.sdk.model.TransactionOverview
|
||||
import cash.z.ecc.android.sdk.model.TransactionState
|
||||
import cash.z.ecc.android.sdk.model.toZecString
|
||||
import cash.z.ecc.sdk.type.ZcashCurrency
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.design.component.Body
|
||||
import co.electriccoin.zcash.ui.design.component.GradientSurface
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.screen.history.HistoryTag
|
||||
import co.electriccoin.zcash.ui.screen.history.state.TransactionHistorySyncState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
@Preview("History")
|
||||
@Composable
|
||||
private fun ComposablePreview() {
|
||||
ZcashTheme(darkTheme = true) {
|
||||
GradientSurface {
|
||||
History(
|
||||
transactionState = TransactionHistorySyncState.Loading,
|
||||
goBack = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val dateFormat: DateFormat by lazy {
|
||||
SimpleDateFormat.getDateTimeInstance(
|
||||
SimpleDateFormat.MEDIUM,
|
||||
SimpleDateFormat.SHORT,
|
||||
Locale.getDefault()
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun History(
|
||||
transactionState: TransactionHistorySyncState,
|
||||
goBack: () -> Unit
|
||||
) {
|
||||
Scaffold(topBar = {
|
||||
HistoryTopBar(onBack = goBack)
|
||||
}) { paddingValues ->
|
||||
HistoryMainContent(
|
||||
transactionState = transactionState,
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.padding(
|
||||
top = paddingValues.calculateTopPadding(),
|
||||
bottom = paddingValues.calculateBottomPadding()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
private fun HistoryTopBar(onBack: () -> Unit) {
|
||||
TopAppBar(
|
||||
title = { Text(text = stringResource(id = R.string.history_title)) },
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
onClick = onBack
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowBack,
|
||||
contentDescription = stringResource(R.string.history_back_content_description)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HistoryMainContent(
|
||||
transactionState: TransactionHistorySyncState,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Box(modifier = modifier.fillMaxSize()) {
|
||||
when (transactionState) {
|
||||
is TransactionHistorySyncState.Loading -> {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.align(alignment = Center)
|
||||
.testTag(HistoryTag.PROGRESS)
|
||||
)
|
||||
}
|
||||
is TransactionHistorySyncState.Syncing -> {
|
||||
Column(
|
||||
modifier = Modifier.align(alignment = TopCenter)
|
||||
) {
|
||||
Body(
|
||||
text = stringResource(id = R.string.history_syncing),
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
top = ZcashTheme.dimens.spacingSmall,
|
||||
bottom = ZcashTheme.dimens.spacingSmall,
|
||||
start = ZcashTheme.dimens.spacingDefault,
|
||||
end = ZcashTheme.dimens.spacingDefault
|
||||
)
|
||||
)
|
||||
HistoryList(transactions = transactionState.transactions)
|
||||
}
|
||||
// Add progress indicator only in the state of empty transaction
|
||||
if (transactionState.hasNoTransactions()) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier
|
||||
.align(alignment = Center)
|
||||
.testTag(HistoryTag.PROGRESS)
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
is TransactionHistorySyncState.Done -> {
|
||||
if (transactionState.hasNoTransactions()) {
|
||||
Body(
|
||||
text = stringResource(id = R.string.history_empty),
|
||||
modifier = Modifier
|
||||
.padding(all = ZcashTheme.dimens.spacingDefault)
|
||||
.align(alignment = Center)
|
||||
)
|
||||
} else {
|
||||
HistoryList(transactions = transactionState.transactions)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HistoryList(transactions: ImmutableList<TransactionOverview>) {
|
||||
val currency = ZcashCurrency.fromResources(LocalContext.current)
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(all = ZcashTheme.dimens.spacingDefault),
|
||||
modifier = Modifier.testTag(HistoryTag.TRANSACTION_LIST)
|
||||
) {
|
||||
items(transactions) {
|
||||
HistoryItem(
|
||||
transaction = it,
|
||||
currency = currency
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Suppress("LongMethod")
|
||||
fun HistoryItem(
|
||||
transaction: TransactionOverview,
|
||||
currency: ZcashCurrency
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = ZcashTheme.dimens.spacingSmall),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
val transactionText: String
|
||||
val transactionIcon: ImageVector
|
||||
when (transaction.getExtendedState()) {
|
||||
TransactionExtendedState.SENT -> {
|
||||
transactionText = stringResource(id = R.string.history_item_sent)
|
||||
transactionIcon = Icons.TwoTone.ArrowCircleUp
|
||||
}
|
||||
TransactionExtendedState.SENDING -> {
|
||||
transactionText = stringResource(id = R.string.history_item_sending)
|
||||
transactionIcon = Icons.Outlined.ArrowCircleUp
|
||||
}
|
||||
TransactionExtendedState.RECEIVED -> {
|
||||
transactionText = stringResource(id = R.string.history_item_received)
|
||||
transactionIcon = Icons.TwoTone.ArrowCircleDown
|
||||
}
|
||||
TransactionExtendedState.RECEIVING -> {
|
||||
transactionText = stringResource(id = R.string.history_item_receiving)
|
||||
transactionIcon = Icons.Outlined.ArrowCircleDown
|
||||
}
|
||||
TransactionExtendedState.EXPIRED -> {
|
||||
transactionText = stringResource(id = R.string.history_item_expired)
|
||||
transactionIcon = Icons.Filled.Cancel
|
||||
}
|
||||
}
|
||||
|
||||
Image(
|
||||
imageVector = transactionIcon,
|
||||
contentDescription = transactionText,
|
||||
modifier = Modifier.padding(all = ZcashTheme.dimens.spacingTiny)
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
) {
|
||||
Body(
|
||||
text = transactionText,
|
||||
color = Color.Black
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingTiny))
|
||||
|
||||
// * 1000 to covert to millis
|
||||
@Suppress("MagicNumber")
|
||||
val dateString = dateFormat.format(transaction.blockTimeEpochSeconds.times(1000))
|
||||
Body(
|
||||
text = dateString,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
|
||||
Column {
|
||||
Row(modifier = Modifier.align(alignment = Alignment.End)) {
|
||||
val zecString = if (transaction.isSentTransaction) {
|
||||
"-${transaction.netValue.toZecString()}"
|
||||
} else {
|
||||
transaction.netValue.toZecString()
|
||||
}
|
||||
Body(text = zecString)
|
||||
|
||||
Spacer(modifier = Modifier.width(ZcashTheme.dimens.spacingTiny))
|
||||
|
||||
Body(text = currency.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class TransactionExtendedState {
|
||||
SENT,
|
||||
SENDING,
|
||||
RECEIVED,
|
||||
RECEIVING,
|
||||
EXPIRED
|
||||
}
|
||||
|
||||
private fun TransactionOverview.getExtendedState(): TransactionExtendedState {
|
||||
return when (transactionState) {
|
||||
TransactionState.Expired -> {
|
||||
TransactionExtendedState.EXPIRED
|
||||
}
|
||||
TransactionState.Confirmed -> {
|
||||
if (isSentTransaction) {
|
||||
TransactionExtendedState.SENT
|
||||
} else {
|
||||
TransactionExtendedState.RECEIVED
|
||||
}
|
||||
}
|
||||
TransactionState.Pending -> {
|
||||
if (isSentTransaction) {
|
||||
TransactionExtendedState.SENDING
|
||||
} else {
|
||||
TransactionExtendedState.RECEIVING
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
error("Unexpected transaction state found while calculating its extended state.")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -36,6 +36,7 @@ internal fun MainActivity.WrapHome(
|
|||
goAbout: () -> Unit,
|
||||
goReceive: () -> Unit,
|
||||
goSend: () -> Unit,
|
||||
goHistory: () -> Unit
|
||||
) {
|
||||
WrapHome(
|
||||
this,
|
||||
|
@ -45,6 +46,7 @@ internal fun MainActivity.WrapHome(
|
|||
goAbout = goAbout,
|
||||
goReceive = goReceive,
|
||||
goSend = goSend,
|
||||
goHistory = goHistory,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -58,6 +60,7 @@ internal fun WrapHome(
|
|||
goAbout: () -> Unit,
|
||||
goReceive: () -> Unit,
|
||||
goSend: () -> Unit,
|
||||
goHistory: () -> Unit,
|
||||
) {
|
||||
// we want to show information about app update, if available
|
||||
val checkUpdateViewModel by activity.viewModels<CheckUpdateViewModel> {
|
||||
|
@ -91,13 +94,10 @@ internal fun WrapHome(
|
|||
!FirebaseTestLabUtil.isFirebaseTestLab(context) &&
|
||||
!EmulatorWtfUtil.isEmulatorWtf(context)
|
||||
|
||||
val transactionSnapshot = walletViewModel.transactionSnapshot.collectAsStateWithLifecycle().value
|
||||
|
||||
val drawerValues = drawerBackHandler()
|
||||
|
||||
Home(
|
||||
walletSnapshot,
|
||||
transactionSnapshot,
|
||||
isUpdateAvailable = updateAvailable,
|
||||
isKeepScreenOnDuringSync = isKeepScreenOnWhileSyncing,
|
||||
isFiatConversionEnabled = isFiatConversionEnabled,
|
||||
|
@ -109,6 +109,7 @@ internal fun WrapHome(
|
|||
goAbout = goAbout,
|
||||
goReceive = goReceive,
|
||||
goSend = goSend,
|
||||
goHistory = goHistory,
|
||||
resetSdk = {
|
||||
walletViewModel.resetSdk()
|
||||
},
|
||||
|
|
|
@ -9,6 +9,7 @@ import cash.z.ecc.android.sdk.model.Zatoshi
|
|||
import co.electriccoin.zcash.ui.screen.home.viewmodel.SynchronizerError
|
||||
|
||||
// TODO [#292]: Should be moved to SDK-EXT-UI module.
|
||||
// TODO [#292]: https://github.com/zcash/secant-android-wallet/issues/292
|
||||
data class WalletSnapshot(
|
||||
val status: Synchronizer.Status,
|
||||
val processorInfo: CompactBlockProcessor.ProcessorInfo,
|
||||
|
|
|
@ -13,8 +13,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
|
@ -58,7 +56,6 @@ import androidx.compose.ui.unit.dp
|
|||
import cash.z.ecc.android.sdk.Synchronizer
|
||||
import cash.z.ecc.android.sdk.model.FiatCurrencyConversionRateState
|
||||
import cash.z.ecc.android.sdk.model.PercentDecimal
|
||||
import cash.z.ecc.android.sdk.model.TransactionOverview
|
||||
import co.electriccoin.zcash.crash.android.GlobalCrashReporter
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.DisableScreenTimeout
|
||||
|
@ -69,13 +66,12 @@ import co.electriccoin.zcash.ui.design.component.BodyWithFiatCurrencySymbol
|
|||
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.TertiaryButton
|
||||
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.model.WalletSnapshot
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
@Preview("Home")
|
||||
|
@ -85,7 +81,6 @@ private fun ComposablePreview() {
|
|||
GradientSurface {
|
||||
Home(
|
||||
walletSnapshot = WalletSnapshotFixture.new(),
|
||||
transactionHistory = persistentListOf(),
|
||||
isUpdateAvailable = false,
|
||||
isKeepScreenOnDuringSync = false,
|
||||
isDebugMenuEnabled = false,
|
||||
|
@ -97,6 +92,7 @@ private fun ComposablePreview() {
|
|||
goAbout = {},
|
||||
goReceive = {},
|
||||
goSend = {},
|
||||
goHistory = {},
|
||||
resetSdk = {},
|
||||
drawerState = rememberDrawerState(DrawerValue.Closed),
|
||||
scope = rememberCoroutineScope()
|
||||
|
@ -109,7 +105,6 @@ private fun ComposablePreview() {
|
|||
@Composable
|
||||
fun Home(
|
||||
walletSnapshot: WalletSnapshot,
|
||||
transactionHistory: ImmutableList<TransactionOverview>,
|
||||
isUpdateAvailable: Boolean,
|
||||
isKeepScreenOnDuringSync: Boolean?,
|
||||
isFiatConversionEnabled: Boolean,
|
||||
|
@ -121,6 +116,7 @@ fun Home(
|
|||
goAbout: () -> Unit,
|
||||
goReceive: () -> Unit,
|
||||
goSend: () -> Unit,
|
||||
goHistory: () -> Unit,
|
||||
resetSdk: () -> Unit,
|
||||
drawerState: DrawerState,
|
||||
scope: CoroutineScope
|
||||
|
@ -145,14 +141,14 @@ fun Home(
|
|||
)
|
||||
}) { paddingValues ->
|
||||
HomeMainContent(
|
||||
walletSnapshot,
|
||||
transactionHistory,
|
||||
walletSnapshot = walletSnapshot,
|
||||
isUpdateAvailable = isUpdateAvailable,
|
||||
isKeepScreenOnDuringSync = isKeepScreenOnDuringSync,
|
||||
isFiatConversionEnabled = isFiatConversionEnabled,
|
||||
isCircularProgressBarEnabled = isCircularProgressBarEnabled,
|
||||
goReceive = goReceive,
|
||||
goSend = goSend,
|
||||
goHistory = goHistory,
|
||||
modifier = Modifier.padding(
|
||||
top = paddingValues.calculateTopPadding() + ZcashTheme.dimens.spacingDefault,
|
||||
bottom = paddingValues.calculateBottomPadding() + ZcashTheme.dimens.spacingDefault,
|
||||
|
@ -296,13 +292,13 @@ private fun HomeDrawer(
|
|||
@Composable
|
||||
private fun HomeMainContent(
|
||||
walletSnapshot: WalletSnapshot,
|
||||
transactionHistory: ImmutableList<TransactionOverview>,
|
||||
isUpdateAvailable: Boolean,
|
||||
isKeepScreenOnDuringSync: Boolean?,
|
||||
isFiatConversionEnabled: Boolean,
|
||||
isCircularProgressBarEnabled: Boolean,
|
||||
goReceive: () -> Unit,
|
||||
goSend: () -> Unit,
|
||||
goHistory: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Column(
|
||||
|
@ -339,7 +335,7 @@ private fun HomeMainContent(
|
|||
)
|
||||
)
|
||||
|
||||
History(transactionHistory)
|
||||
TertiaryButton(onClick = goHistory, text = stringResource(R.string.home_button_history))
|
||||
|
||||
if (isKeepScreenOnDuringSync == true && walletSnapshot.status == Synchronizer.Status.SYNCING) {
|
||||
DisableScreenTimeout()
|
||||
|
@ -449,25 +445,3 @@ private fun Status(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Suppress("MagicNumber")
|
||||
private fun History(transactionHistory: ImmutableList<TransactionOverview>) {
|
||||
if (transactionHistory.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
// here we need to use a fixed height to avoid nested columns vertical scrolling problem
|
||||
// we'll refactor this part to a dedicated bottom sheet later
|
||||
val historyPart = LocalConfiguration.current.screenHeightDp / 3
|
||||
|
||||
LazyColumn(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.height(historyPart.dp)
|
||||
) {
|
||||
items(transactionHistory) {
|
||||
Text(it.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,9 +28,8 @@ import co.electriccoin.zcash.ui.preference.EncryptedPreferenceKeys
|
|||
import co.electriccoin.zcash.ui.preference.EncryptedPreferenceSingleton
|
||||
import co.electriccoin.zcash.ui.preference.StandardPreferenceKeys
|
||||
import co.electriccoin.zcash.ui.preference.StandardPreferenceSingleton
|
||||
import co.electriccoin.zcash.ui.screen.history.state.TransactionHistorySyncState
|
||||
import co.electriccoin.zcash.ui.screen.home.model.WalletSnapshot
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
|
@ -148,22 +147,6 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
|
|||
null
|
||||
)
|
||||
|
||||
// This is not the right API, because the transaction list could be very long and might need UI filtering
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val transactionSnapshot: StateFlow<ImmutableList<TransactionOverview>> = synchronizer
|
||||
.flatMapLatest {
|
||||
if (null == it) {
|
||||
flowOf(persistentListOf())
|
||||
} else {
|
||||
it.transactions.map { list -> list.toPersistentList() }
|
||||
}
|
||||
}
|
||||
.stateIn(
|
||||
viewModelScope,
|
||||
SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||
persistentListOf()
|
||||
)
|
||||
|
||||
val addresses: StateFlow<WalletAddresses?> = synchronizer
|
||||
.filterNotNull()
|
||||
.map {
|
||||
|
@ -174,6 +157,25 @@ class WalletViewModel(application: Application) : AndroidViewModel(application)
|
|||
null
|
||||
)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
val transactionHistoryState = synchronizer
|
||||
.filterNotNull()
|
||||
.flatMapLatest {
|
||||
it.transactions
|
||||
.combine(it.status) { transactions: List<TransactionOverview>, status: Synchronizer.Status ->
|
||||
if (status.isSyncing()) {
|
||||
TransactionHistorySyncState.Syncing(transactions.toPersistentList())
|
||||
} else {
|
||||
TransactionHistorySyncState.Done(transactions.toPersistentList())
|
||||
}
|
||||
}
|
||||
}
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(ANDROID_STATE_FLOW_TIMEOUT),
|
||||
initialValue = TransactionHistorySyncState.Loading
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates a wallet asynchronously and then persists it. Clients observe
|
||||
* [secretState] to see the side effects. This would be used for a user creating a new wallet.
|
||||
|
@ -347,3 +349,5 @@ private fun Synchronizer.toWalletSnapshot() =
|
|||
flows[6] as SynchronizerError?
|
||||
)
|
||||
}
|
||||
|
||||
private fun Synchronizer.Status.isSyncing() = this == Synchronizer.Status.SYNCING
|
||||
|
|
|
@ -8,11 +8,11 @@ import androidx.activity.viewModels
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import cash.z.ecc.android.sdk.fixture.WalletFixture
|
||||
import cash.z.ecc.android.sdk.model.BlockHeight
|
||||
import cash.z.ecc.android.sdk.model.PersistableWallet
|
||||
import cash.z.ecc.android.sdk.model.SeedPhrase
|
||||
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||
import cash.z.ecc.sdk.fixture.SeedPhraseFixture
|
||||
import cash.z.ecc.sdk.type.fromResources
|
||||
import co.electriccoin.zcash.spackle.EmulatorWtfUtil
|
||||
import co.electriccoin.zcash.spackle.FirebaseTestLabUtil
|
||||
|
@ -54,8 +54,8 @@ internal fun WrapOnboarding(
|
|||
persistExistingWalletWithSeedPhrase(
|
||||
applicationContext,
|
||||
walletViewModel,
|
||||
SeedPhraseFixture.new(),
|
||||
birthday = null
|
||||
SeedPhrase.new(WalletFixture.Alice.seedPhrase),
|
||||
birthday = WalletFixture.Alice.getBirthday(ZcashNetwork.fromResources(applicationContext))
|
||||
)
|
||||
} else {
|
||||
walletViewModel.persistNewWallet()
|
||||
|
@ -71,8 +71,8 @@ internal fun WrapOnboarding(
|
|||
persistExistingWalletWithSeedPhrase(
|
||||
applicationContext,
|
||||
walletViewModel,
|
||||
SeedPhraseFixture.new(),
|
||||
birthday = null
|
||||
SeedPhrase.new(WalletFixture.Alice.seedPhrase),
|
||||
birthday = WalletFixture.Alice.getBirthday(ZcashNetwork.fromResources(applicationContext))
|
||||
)
|
||||
} else {
|
||||
onboardingViewModel.setIsImporting(true)
|
||||
|
@ -83,8 +83,8 @@ internal fun WrapOnboarding(
|
|||
persistExistingWalletWithSeedPhrase(
|
||||
applicationContext,
|
||||
walletViewModel,
|
||||
SeedPhraseFixture.new(),
|
||||
birthday = null
|
||||
SeedPhrase.new(WalletFixture.Alice.seedPhrase),
|
||||
birthday = WalletFixture.Alice.getBirthday(ZcashNetwork.fromResources(applicationContext))
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cash.z.ecc.android.sdk.fixture.WalletAddressFixture
|
||||
import cash.z.ecc.android.sdk.model.MonetarySeparators
|
||||
|
@ -42,8 +43,9 @@ import co.electriccoin.zcash.ui.design.component.PrimaryButton
|
|||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
@Preview("Request")
|
||||
@Composable
|
||||
fun PreviewRequest() {
|
||||
private fun PreviewRequest() {
|
||||
ZcashTheme(darkTheme = true) {
|
||||
GradientSurface {
|
||||
Request(
|
||||
|
@ -58,7 +60,6 @@ fun PreviewRequest() {
|
|||
/**
|
||||
* @param myAddress The address that ZEC should be sent to.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun Request(
|
||||
myAddress: WalletAddress.Unified,
|
||||
|
@ -98,7 +99,6 @@ private fun RequestTopAppBar(onBack: () -> Unit) {
|
|||
// TODO [#217]: Need to handle changing of Locale after user input, but before submitting the button.
|
||||
// TODO [#288]: TextField component can't do long-press backspace.
|
||||
@Composable
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
private fun RequestMainContent(
|
||||
paddingValues: PaddingValues,
|
||||
myAddress: WalletAddress.Unified,
|
||||
|
|
|
@ -17,6 +17,7 @@ data class TimeInfo(
|
|||
) {
|
||||
|
||||
// TODO [#388]: Consider fuzzing the times
|
||||
// TODO [#388]: https://github.com/zcash/secant-android-wallet/issues/388
|
||||
fun toSupportString() = buildString {
|
||||
// Use a slightly more human friendly format instead of ISO, since this will appear in the emails that users see
|
||||
val dateFormat = SimpleDateFormat("yyyy-MM-dd hh:mm:ss a", Locale.US) // $NON-NLS-1$
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="history_title">Transaction history</string>
|
||||
<string name="history_back_content_description">Back</string>
|
||||
<string name="history_syncing">Additional transactions may be found after syncing completes…</string>
|
||||
<string name="history_empty">No transactions yet</string>
|
||||
|
||||
<string name="history_item_sent">Sent</string>
|
||||
<string name="history_item_received">Received</string>
|
||||
<string name="history_item_sending">Sending</string>
|
||||
<string name="history_item_receiving">Receiving</string>
|
||||
<string name="history_item_expired">Expired</string>
|
||||
|
||||
</resources>
|
|
@ -2,6 +2,7 @@
|
|||
<string name="home_menu_content_description">Open menu</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_information">You won’t be able to transfer funds until your wallet is finished syncing. Please keep your device plugged in and the app open.</string>
|
||||
|
||||
<string name="home_menu_seed_phrase">My secret phrase</string>
|
||||
|
|
|
@ -145,7 +145,7 @@ class ScreenshotTest : UiTestPrerequisites() {
|
|||
}
|
||||
|
||||
// TODO [#859]: Screenshot tests fail on Firebase Test Lab
|
||||
// https://github.com/zcash/secant-android-wallet/issues/859
|
||||
// TODO [#859]: https://github.com/zcash/secant-android-wallet/issues/859
|
||||
// Some of the restore screenshots broke with the Compose 1.4 update and we don't yet know why.
|
||||
private val isRestoreScreenshotsEnabled = false
|
||||
|
||||
|
@ -312,6 +312,9 @@ class ScreenshotTest : UiTestPrerequisites() {
|
|||
|
||||
navigateTo(NavigationTargets.WALLET_ADDRESS_DETAILS)
|
||||
addressDetailsScreenshots(resContext, tag, composeTestRule)
|
||||
|
||||
navigateTo(NavigationTargets.HISTORY)
|
||||
transactionHistoryScreenshots(resContext, tag, composeTestRule)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -534,6 +537,14 @@ private fun addressDetailsScreenshots(resContext: Context, tag: String, composeT
|
|||
ScreenshotTest.takeScreenshot(tag, "Addresses 1")
|
||||
}
|
||||
|
||||
private fun transactionHistoryScreenshots(resContext: Context, tag: String, composeTestRule: ComposeTestRule) {
|
||||
composeTestRule.onNode(hasText(resContext.getString(R.string.history_title))).also {
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
ScreenshotTest.takeScreenshot(tag, "Transaction History 1")
|
||||
}
|
||||
|
||||
// This screen is not currently navigable from the app
|
||||
@Suppress("UnusedPrivateMember")
|
||||
private fun requestZecScreenshots(resContext: Context, tag: String, composeTestRule: ComposeTestRule) {
|
||||
|
|
Loading…
Reference in New Issue