[#791] Add Scan To Send
* [#791] Add Scan Button To Send Screen - Switched Send.Form screen input fields ordering to match the MFP design - Added Scan button to Send.Form - Changed navigation to pass the scanned result to the Send.Form screen - Added leading and trailing icons to FormTextField component - Created SendArgumentsWrapper to pass any future Zcash URL parsed parameters * Report invalid address scanned - Merged composes to provide snackbar after invalid address scanned * Add SendView tests - Added one for scanner click and one for initial send arguments inserted - This led to input and check amount and memo as part of the SendArgumentsWrapper too to have it prepare for future Zcash URI parsing * Static analysis warnings fixes * Don't make camera feature required - And hide the scanner button on Send.Form when any camera is not supported by the device * Let SendArgumentsWrapperFixture use SDK fixtures * Tweak hint text We need better text, but this is at least more correct than the previous iteration. * Replace assertTextContains with assertTextEquals - To compare inputs texts to an exact match to avoid potentially missing bugs if there is extra text in the field that we're not expecting * Fix ktlint warning --------- Co-authored-by: Carter Jernigan <git@carterjernigan.com>
This commit is contained in:
parent
136e56ea9a
commit
5296dc5ea9
|
@ -57,5 +57,5 @@ This section is optional and is required only if you'd like to test on an Androi
|
||||||
1. Grant the Camera permission with one of the previous procedures
|
1. Grant the Camera permission with one of the previous procedures
|
||||||
1. Create QR code from the valid Zcash wallet address. You can use, for example, the [QR Code Generator](https://www.qr-code-generator.com/) tool.
|
1. Create QR code from the valid Zcash wallet address. You can use, for example, the [QR Code Generator](https://www.qr-code-generator.com/) tool.
|
||||||
1. Scan the created QR code
|
1. Scan the created QR code
|
||||||
1. The code should be scanned but not validated
|
1. The code should be scanned but not validated (error message is displayed).
|
||||||
1. The app UI should not be changed and the Camera view should be still available for scanning another codes
|
1. The app UI should not be changed and the Camera view should be still available for scanning another codes
|
|
@ -18,6 +18,8 @@ fun FormTextField(
|
||||||
onValueChange: (String) -> Unit,
|
onValueChange: (String) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
label: @Composable (() -> Unit)? = null,
|
label: @Composable (() -> Unit)? = null,
|
||||||
|
leadingIcon: @Composable (() -> Unit)? = null,
|
||||||
|
trailingIcon: @Composable (() -> Unit)? = null,
|
||||||
keyboardOptions: KeyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text),
|
keyboardOptions: KeyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text),
|
||||||
colors: TextFieldColors = TextFieldDefaults.textFieldColors(
|
colors: TextFieldColors = TextFieldDefaults.textFieldColors(
|
||||||
containerColor = Color.Transparent
|
containerColor = Color.Transparent
|
||||||
|
@ -29,6 +31,8 @@ fun FormTextField(
|
||||||
label = label,
|
label = label,
|
||||||
keyboardOptions = keyboardOptions,
|
keyboardOptions = keyboardOptions,
|
||||||
colors = colors,
|
colors = colors,
|
||||||
modifier = modifier
|
modifier = modifier,
|
||||||
|
leadingIcon = leadingIcon,
|
||||||
|
trailingIcon = trailingIcon
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package co.electriccoin.zcash.ui.fixture
|
||||||
|
|
||||||
|
import cash.z.ecc.android.sdk.fixture.WalletFixture
|
||||||
|
import cash.z.ecc.android.sdk.model.Zatoshi
|
||||||
|
import cash.z.ecc.android.sdk.model.ZcashNetwork
|
||||||
|
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.screen.send.model.SendArgumentsWrapper
|
||||||
|
|
||||||
|
internal object SendArgumentsWrapperFixture {
|
||||||
|
val RECIPIENT_ADDRESS = WalletFixture.Alice.getAddresses(ZcashNetwork.Testnet).unified
|
||||||
|
val MEMO = MemoFixture.new("Thanks for lunch").value
|
||||||
|
val AMOUNT = ZatoshiFixture.new(1)
|
||||||
|
fun amountToFixtureZecString(amount: Zatoshi?) = amount?.toZecString()
|
||||||
|
|
||||||
|
fun new(
|
||||||
|
recipientAddress: String? = RECIPIENT_ADDRESS,
|
||||||
|
amount: Zatoshi? = AMOUNT,
|
||||||
|
memo: String? = MEMO
|
||||||
|
) = SendArgumentsWrapper(
|
||||||
|
recipientAddress = recipientAddress,
|
||||||
|
amount = amountToFixtureZecString(amount),
|
||||||
|
memo = memo
|
||||||
|
)
|
||||||
|
}
|
|
@ -20,6 +20,12 @@ internal fun ComposeContentTestRule.clickBack() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun ComposeContentTestRule.clickScanner() {
|
||||||
|
onNodeWithContentDescription(getStringResource(R.string.send_scan_content_description)).also {
|
||||||
|
it.performClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal fun ComposeContentTestRule.setValidAmount() {
|
internal fun ComposeContentTestRule.setValidAmount() {
|
||||||
onNodeWithText(getStringResource(R.string.send_amount)).also {
|
onNodeWithText(getStringResource(R.string.send_amount)).also {
|
||||||
val separators = MonetarySeparators.current()
|
val separators = MonetarySeparators.current()
|
||||||
|
|
|
@ -9,6 +9,7 @@ import cash.z.ecc.android.sdk.model.ZecSend
|
||||||
import cash.z.ecc.sdk.fixture.ZatoshiFixture
|
import cash.z.ecc.sdk.fixture.ZatoshiFixture
|
||||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||||
import co.electriccoin.zcash.ui.screen.send.ext.Saver
|
import co.electriccoin.zcash.ui.screen.send.ext.Saver
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.model.SendArgumentsWrapper
|
||||||
import co.electriccoin.zcash.ui.screen.send.model.SendStage
|
import co.electriccoin.zcash.ui.screen.send.model.SendStage
|
||||||
import co.electriccoin.zcash.ui.screen.send.view.Send
|
import co.electriccoin.zcash.ui.screen.send.view.Send
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
@ -18,10 +19,13 @@ import java.util.concurrent.atomic.AtomicInteger
|
||||||
class SendViewTestSetup(
|
class SendViewTestSetup(
|
||||||
private val composeTestRule: ComposeContentTestRule,
|
private val composeTestRule: ComposeContentTestRule,
|
||||||
private val initialState: SendStage,
|
private val initialState: SendStage,
|
||||||
private val initialZecSend: ZecSend?
|
private val initialZecSend: ZecSend?,
|
||||||
|
private val initialSendArgumentWrapper: SendArgumentsWrapper?,
|
||||||
|
private val hasCameraFeature: Boolean
|
||||||
) {
|
) {
|
||||||
private val onBackCount = AtomicInteger(0)
|
private val onBackCount = AtomicInteger(0)
|
||||||
private val onCreateCount = AtomicInteger(0)
|
private val onCreateCount = AtomicInteger(0)
|
||||||
|
private val onScannerCount = AtomicInteger(0)
|
||||||
val mutableActionExecuted = MutableStateFlow(false)
|
val mutableActionExecuted = MutableStateFlow(false)
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
|
@ -40,6 +44,11 @@ class SendViewTestSetup(
|
||||||
return onCreateCount.get()
|
return onCreateCount.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getOnScannerCount(): Int {
|
||||||
|
composeTestRule.waitForIdle()
|
||||||
|
return onScannerCount.get()
|
||||||
|
}
|
||||||
|
|
||||||
fun getLastZecSend(): ZecSend? {
|
fun getLastZecSend(): ZecSend? {
|
||||||
composeTestRule.waitForIdle()
|
composeTestRule.waitForIdle()
|
||||||
return lastZecSend
|
return lastZecSend
|
||||||
|
@ -82,6 +91,7 @@ class SendViewTestSetup(
|
||||||
Send(
|
Send(
|
||||||
mySpendableBalance = ZatoshiFixture.new(),
|
mySpendableBalance = ZatoshiFixture.new(),
|
||||||
sendStage = sendStage,
|
sendStage = sendStage,
|
||||||
|
sendArgumentsWrapper = initialSendArgumentWrapper,
|
||||||
onSendStageChange = setSendStage,
|
onSendStageChange = setSendStage,
|
||||||
zecSend = zecSend,
|
zecSend = zecSend,
|
||||||
onZecSendChange = setZecSend,
|
onZecSendChange = setZecSend,
|
||||||
|
@ -90,7 +100,11 @@ class SendViewTestSetup(
|
||||||
onCreateCount.incrementAndGet()
|
onCreateCount.incrementAndGet()
|
||||||
lastZecSend = it
|
lastZecSend = it
|
||||||
mutableActionExecuted.update { true }
|
mutableActionExecuted.update { true }
|
||||||
}
|
},
|
||||||
|
onQrScannerOpen = {
|
||||||
|
onScannerCount.incrementAndGet()
|
||||||
|
},
|
||||||
|
hasCameraFeature = hasCameraFeature
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package co.electriccoin.zcash.ui.screen.send.integration
|
package co.electriccoin.zcash.ui.screen.send.integration
|
||||||
|
|
||||||
import androidx.compose.ui.test.assertTextContains
|
import androidx.compose.ui.test.assertTextEquals
|
||||||
import androidx.compose.ui.test.junit4.StateRestorationTester
|
import androidx.compose.ui.test.junit4.StateRestorationTester
|
||||||
import androidx.compose.ui.test.junit4.createComposeRule
|
import androidx.compose.ui.test.junit4.createComposeRule
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
@ -51,10 +51,13 @@ class SendViewIntegrationTest {
|
||||||
|
|
||||||
restorationTester.setContent {
|
restorationTester.setContent {
|
||||||
WrapSend(
|
WrapSend(
|
||||||
|
sendArgumentsWrapper = null,
|
||||||
synchronizer = synchronizer,
|
synchronizer = synchronizer,
|
||||||
spendableBalance = balance,
|
spendableBalance = balance,
|
||||||
spendingKey = spendingKey,
|
spendingKey = spendingKey,
|
||||||
goBack = {}
|
goToQrScanner = {},
|
||||||
|
goBack = {},
|
||||||
|
hasCameraFeature = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,14 +80,24 @@ class SendViewIntegrationTest {
|
||||||
composeTestRule.clickBack()
|
composeTestRule.clickBack()
|
||||||
composeTestRule.assertOnForm()
|
composeTestRule.assertOnForm()
|
||||||
|
|
||||||
// And check recreated form values too
|
// And check recreated form values too. Note also that we don't check the amount field value, as it's changed
|
||||||
// We use that the assertTextContains searches in SemanticsProperties.EditableText too
|
// by validation mechanisms
|
||||||
// Note also that we don't check the amount field value, as it's changed by validation mechanisms
|
|
||||||
|
// We use that the assertTextEquals searches in SemanticsProperties.EditableText too, although to be able to
|
||||||
|
// compare its editable value to an exact match we need to pass all its texts
|
||||||
composeTestRule.onNodeWithText(getStringResource(R.string.send_to)).also {
|
composeTestRule.onNodeWithText(getStringResource(R.string.send_to)).also {
|
||||||
it.assertTextContains(ZecSendFixture.ADDRESS)
|
it.assertTextEquals(
|
||||||
|
getStringResource(R.string.send_to),
|
||||||
|
ZecSendFixture.ADDRESS,
|
||||||
|
includeEditableText = true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
composeTestRule.onNodeWithText(getStringResource(R.string.send_memo)).also {
|
composeTestRule.onNodeWithText(getStringResource(R.string.send_memo)).also {
|
||||||
it.assertTextContains(ZecSendFixture.MEMO.value)
|
it.assertTextEquals(
|
||||||
|
getStringResource(R.string.send_memo),
|
||||||
|
ZecSendFixture.MEMO.value,
|
||||||
|
includeEditableText = true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,9 @@ class SendViewAndroidTest : UiTestPrerequisites() {
|
||||||
) = SendViewTestSetup(
|
) = SendViewTestSetup(
|
||||||
composeTestRule,
|
composeTestRule,
|
||||||
sendStage,
|
sendStage,
|
||||||
zecSend
|
zecSend,
|
||||||
|
null,
|
||||||
|
true
|
||||||
).apply {
|
).apply {
|
||||||
setDefaultContent()
|
setDefaultContent()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package co.electriccoin.zcash.ui.screen.send.view
|
package co.electriccoin.zcash.ui.screen.send.view
|
||||||
|
|
||||||
import androidx.compose.ui.test.assertIsNotEnabled
|
import androidx.compose.ui.test.assertIsNotEnabled
|
||||||
|
import androidx.compose.ui.test.assertTextEquals
|
||||||
import androidx.compose.ui.test.junit4.createComposeRule
|
import androidx.compose.ui.test.junit4.createComposeRule
|
||||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
@ -16,6 +17,7 @@ import cash.z.ecc.sdk.fixture.ZecRequestFixture
|
||||||
import cash.z.ecc.sdk.fixture.ZecSendFixture
|
import cash.z.ecc.sdk.fixture.ZecSendFixture
|
||||||
import co.electriccoin.zcash.test.UiTestPrerequisites
|
import co.electriccoin.zcash.test.UiTestPrerequisites
|
||||||
import co.electriccoin.zcash.ui.R
|
import co.electriccoin.zcash.ui.R
|
||||||
|
import co.electriccoin.zcash.ui.fixture.SendArgumentsWrapperFixture
|
||||||
import co.electriccoin.zcash.ui.screen.send.SendViewTestSetup
|
import co.electriccoin.zcash.ui.screen.send.SendViewTestSetup
|
||||||
import co.electriccoin.zcash.ui.screen.send.assertOnConfirmation
|
import co.electriccoin.zcash.ui.screen.send.assertOnConfirmation
|
||||||
import co.electriccoin.zcash.ui.screen.send.assertOnForm
|
import co.electriccoin.zcash.ui.screen.send.assertOnForm
|
||||||
|
@ -27,6 +29,8 @@ import co.electriccoin.zcash.ui.screen.send.assertSendEnabled
|
||||||
import co.electriccoin.zcash.ui.screen.send.clickBack
|
import co.electriccoin.zcash.ui.screen.send.clickBack
|
||||||
import co.electriccoin.zcash.ui.screen.send.clickConfirmation
|
import co.electriccoin.zcash.ui.screen.send.clickConfirmation
|
||||||
import co.electriccoin.zcash.ui.screen.send.clickCreateAndSend
|
import co.electriccoin.zcash.ui.screen.send.clickCreateAndSend
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.clickScanner
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.model.SendArgumentsWrapper
|
||||||
import co.electriccoin.zcash.ui.screen.send.model.SendStage
|
import co.electriccoin.zcash.ui.screen.send.model.SendStage
|
||||||
import co.electriccoin.zcash.ui.screen.send.setAmount
|
import co.electriccoin.zcash.ui.screen.send.setAmount
|
||||||
import co.electriccoin.zcash.ui.screen.send.setMemo
|
import co.electriccoin.zcash.ui.screen.send.setMemo
|
||||||
|
@ -51,11 +55,15 @@ class SendViewTest : UiTestPrerequisites() {
|
||||||
|
|
||||||
private fun newTestSetup(
|
private fun newTestSetup(
|
||||||
sendStage: SendStage = SendStage.Form,
|
sendStage: SendStage = SendStage.Form,
|
||||||
zecSend: ZecSend? = null
|
zecSend: ZecSend? = null,
|
||||||
|
sendArgumentsWrapper: SendArgumentsWrapper? = null,
|
||||||
|
hasCameraFeature: Boolean = true
|
||||||
) = SendViewTestSetup(
|
) = SendViewTestSetup(
|
||||||
composeTestRule,
|
composeTestRule,
|
||||||
sendStage,
|
sendStage,
|
||||||
zecSend
|
zecSend,
|
||||||
|
sendArgumentsWrapper,
|
||||||
|
hasCameraFeature
|
||||||
).apply {
|
).apply {
|
||||||
setDefaultContent()
|
setDefaultContent()
|
||||||
}
|
}
|
||||||
|
@ -395,4 +403,86 @@ class SendViewTest : UiTestPrerequisites() {
|
||||||
|
|
||||||
assertEquals(1, testSetup.getOnBackCount())
|
assertEquals(1, testSetup.getOnBackCount())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@MediumTest
|
||||||
|
fun scanner_button_on_form_hit() {
|
||||||
|
val testSetup = newTestSetup()
|
||||||
|
|
||||||
|
assertEquals(0, testSetup.getOnScannerCount())
|
||||||
|
|
||||||
|
composeTestRule.clickScanner()
|
||||||
|
|
||||||
|
assertEquals(1, testSetup.getOnScannerCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@MediumTest
|
||||||
|
fun input_arguments_to_form() {
|
||||||
|
newTestSetup(
|
||||||
|
sendStage = SendStage.Form,
|
||||||
|
sendArgumentsWrapper = SendArgumentsWrapperFixture.new(
|
||||||
|
recipientAddress = SendArgumentsWrapperFixture.RECIPIENT_ADDRESS,
|
||||||
|
amount = SendArgumentsWrapperFixture.AMOUNT,
|
||||||
|
memo = SendArgumentsWrapperFixture.MEMO
|
||||||
|
),
|
||||||
|
zecSend = null
|
||||||
|
)
|
||||||
|
|
||||||
|
composeTestRule.assertOnForm()
|
||||||
|
|
||||||
|
// We use that the assertTextEquals searches in SemanticsProperties.EditableText too, although to be able to
|
||||||
|
// compare its editable value to an exact match we need to pass all its texts
|
||||||
|
composeTestRule.onNodeWithText(getStringResource(R.string.send_to)).also {
|
||||||
|
it.assertTextEquals(
|
||||||
|
getStringResource(R.string.send_to),
|
||||||
|
SendArgumentsWrapperFixture.RECIPIENT_ADDRESS,
|
||||||
|
includeEditableText = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composeTestRule.onNodeWithText(getStringResource(R.string.send_amount)).also {
|
||||||
|
it.assertTextEquals(
|
||||||
|
getStringResource(R.string.send_amount),
|
||||||
|
SendArgumentsWrapperFixture.amountToFixtureZecString(SendArgumentsWrapperFixture.AMOUNT)!!,
|
||||||
|
includeEditableText = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composeTestRule.onNodeWithText(getStringResource(R.string.send_memo)).also {
|
||||||
|
it.assertTextEquals(
|
||||||
|
getStringResource(R.string.send_memo),
|
||||||
|
SendArgumentsWrapperFixture.MEMO,
|
||||||
|
includeEditableText = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@MediumTest
|
||||||
|
fun device_has_camera_feature() {
|
||||||
|
newTestSetup(
|
||||||
|
sendStage = SendStage.Form,
|
||||||
|
hasCameraFeature = true
|
||||||
|
)
|
||||||
|
|
||||||
|
composeTestRule.assertOnForm()
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithContentDescription(getStringResource(R.string.send_scan_content_description)).also {
|
||||||
|
it.assertExists()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@MediumTest
|
||||||
|
fun device_has_not_camera_feature() {
|
||||||
|
newTestSetup(
|
||||||
|
sendStage = SendStage.Form,
|
||||||
|
hasCameraFeature = false
|
||||||
|
)
|
||||||
|
|
||||||
|
composeTestRule.assertOnForm()
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithContentDescription(getStringResource(R.string.send_scan_content_description)).also {
|
||||||
|
it.assertDoesNotExist()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
|
||||||
|
<uses-feature android:name="android.hardware.camera.any" android:required="false" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:icon="@mipmap/ic_launcher_square"
|
android:icon="@mipmap/ic_launcher_square"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
|
|
@ -7,6 +7,10 @@ import androidx.navigation.NavOptionsBuilder
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
|
import co.electriccoin.zcash.spackle.Twig
|
||||||
|
import co.electriccoin.zcash.ui.NavigationArguments.SEND_AMOUNT
|
||||||
|
import co.electriccoin.zcash.ui.NavigationArguments.SEND_MEMO
|
||||||
|
import co.electriccoin.zcash.ui.NavigationArguments.SEND_RECIPIENT_ADDRESS
|
||||||
import co.electriccoin.zcash.ui.NavigationTargets.ABOUT
|
import co.electriccoin.zcash.ui.NavigationTargets.ABOUT
|
||||||
import co.electriccoin.zcash.ui.NavigationTargets.HOME
|
import co.electriccoin.zcash.ui.NavigationTargets.HOME
|
||||||
import co.electriccoin.zcash.ui.NavigationTargets.RECEIVE
|
import co.electriccoin.zcash.ui.NavigationTargets.RECEIVE
|
||||||
|
@ -27,6 +31,7 @@ import co.electriccoin.zcash.ui.screen.request.WrapRequest
|
||||||
import co.electriccoin.zcash.ui.screen.scan.WrapScanValidator
|
import co.electriccoin.zcash.ui.screen.scan.WrapScanValidator
|
||||||
import co.electriccoin.zcash.ui.screen.seed.WrapSeed
|
import co.electriccoin.zcash.ui.screen.seed.WrapSeed
|
||||||
import co.electriccoin.zcash.ui.screen.send.WrapSend
|
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.settings.WrapSettings
|
||||||
import co.electriccoin.zcash.ui.screen.support.WrapSupport
|
import co.electriccoin.zcash.ui.screen.support.WrapSupport
|
||||||
import co.electriccoin.zcash.ui.screen.update.WrapCheckForUpdate
|
import co.electriccoin.zcash.ui.screen.update.WrapCheckForUpdate
|
||||||
|
@ -85,8 +90,22 @@ internal fun MainActivity.Navigation() {
|
||||||
composable(REQUEST) {
|
composable(REQUEST) {
|
||||||
WrapRequest(goBack = { navController.popBackStackJustOnce(REQUEST) })
|
WrapRequest(goBack = { navController.popBackStackJustOnce(REQUEST) })
|
||||||
}
|
}
|
||||||
composable(SEND) {
|
composable(SEND) { backStackEntry ->
|
||||||
WrapSend(goBack = { navController.popBackStackJustOnce(SEND) })
|
WrapSend(
|
||||||
|
goToQrScanner = {
|
||||||
|
Twig.debug { "Opening Qr Scanner Screen" }
|
||||||
|
navController.navigateJustOnce(SCAN)
|
||||||
|
},
|
||||||
|
goBack = { navController.popBackStackJustOnce(SEND) },
|
||||||
|
sendArgumentsWrapper = SendArgumentsWrapper(
|
||||||
|
recipientAddress = backStackEntry.savedStateHandle[SEND_RECIPIENT_ADDRESS],
|
||||||
|
amount = backStackEntry.savedStateHandle[SEND_AMOUNT],
|
||||||
|
memo = backStackEntry.savedStateHandle[SEND_MEMO]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
backStackEntry.savedStateHandle.remove<String>(SEND_RECIPIENT_ADDRESS)
|
||||||
|
backStackEntry.savedStateHandle.remove<String>(SEND_AMOUNT)
|
||||||
|
backStackEntry.savedStateHandle.remove<String>(SEND_MEMO)
|
||||||
}
|
}
|
||||||
composable(SUPPORT) {
|
composable(SUPPORT) {
|
||||||
// Pop back stack won't be right if we deep link into support
|
// Pop back stack won't be right if we deep link into support
|
||||||
|
@ -97,11 +116,14 @@ internal fun MainActivity.Navigation() {
|
||||||
}
|
}
|
||||||
composable(SCAN) {
|
composable(SCAN) {
|
||||||
WrapScanValidator(
|
WrapScanValidator(
|
||||||
onScanValid = {
|
onScanValid = { result ->
|
||||||
// TODO [#449] https://github.com/zcash/secant-android-wallet/issues/449
|
// At this point we only pass recipient address
|
||||||
navController.navigateJustOnce(SEND) {
|
navController.previousBackStackEntry?.savedStateHandle?.apply {
|
||||||
popUpTo(HOME) { inclusive = false }
|
set(SEND_RECIPIENT_ADDRESS, result)
|
||||||
|
set(SEND_AMOUNT, null)
|
||||||
|
set(SEND_MEMO, null)
|
||||||
}
|
}
|
||||||
|
navController.popBackStackJustOnce(SCAN)
|
||||||
},
|
},
|
||||||
goBack = { navController.popBackStackJustOnce(SCAN) }
|
goBack = { navController.popBackStackJustOnce(SCAN) }
|
||||||
)
|
)
|
||||||
|
@ -137,6 +159,12 @@ private fun NavHostController.popBackStackJustOnce(currentRouteToBePopped: Strin
|
||||||
popBackStack()
|
popBackStack()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object NavigationArguments {
|
||||||
|
const val SEND_RECIPIENT_ADDRESS = "send_recipient_address"
|
||||||
|
const val SEND_AMOUNT = "send_amount"
|
||||||
|
const val SEND_MEMO = "send_memo"
|
||||||
|
}
|
||||||
|
|
||||||
object NavigationTargets {
|
object NavigationTargets {
|
||||||
const val HOME = "home"
|
const val HOME = "home"
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import co.electriccoin.zcash.ui.MainActivity
|
import co.electriccoin.zcash.ui.MainActivity
|
||||||
import co.electriccoin.zcash.ui.R
|
import co.electriccoin.zcash.ui.R
|
||||||
import co.electriccoin.zcash.ui.screen.home.viewmodel.WalletViewModel
|
import co.electriccoin.zcash.ui.screen.home.viewmodel.WalletViewModel
|
||||||
|
@ -20,7 +19,7 @@ internal fun MainActivity.WrapScanValidator(
|
||||||
onScanValid: (address: String) -> Unit,
|
onScanValid: (address: String) -> Unit,
|
||||||
goBack: () -> Unit
|
goBack: () -> Unit
|
||||||
) {
|
) {
|
||||||
WrapScanValidator(
|
WrapScan(
|
||||||
this,
|
this,
|
||||||
onScanValid = onScanValid,
|
onScanValid = onScanValid,
|
||||||
goBack = goBack
|
goBack = goBack
|
||||||
|
@ -28,7 +27,7 @@ internal fun MainActivity.WrapScanValidator(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun WrapScanValidator(
|
fun WrapScan(
|
||||||
activity: ComponentActivity,
|
activity: ComponentActivity,
|
||||||
onScanValid: (address: String) -> Unit,
|
onScanValid: (address: String) -> Unit,
|
||||||
goBack: () -> Unit
|
goBack: () -> Unit
|
||||||
|
@ -37,50 +36,41 @@ private fun WrapScanValidator(
|
||||||
|
|
||||||
val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value
|
val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value
|
||||||
|
|
||||||
if (synchronizer == null) {
|
|
||||||
// Display loading indicator
|
|
||||||
} else {
|
|
||||||
WrapScan(
|
|
||||||
activity,
|
|
||||||
onScanned = { result ->
|
|
||||||
activity.lifecycleScope.launch {
|
|
||||||
val isAddressValid = !synchronizer.validateAddress(result).isNotValid
|
|
||||||
if (isAddressValid) {
|
|
||||||
onScanValid(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
goBack = goBack
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun WrapScan(
|
|
||||||
activity: ComponentActivity,
|
|
||||||
onScanned: (result: String) -> Unit,
|
|
||||||
goBack: () -> Unit
|
|
||||||
) {
|
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
Scan(
|
if (synchronizer == null) {
|
||||||
snackbarHostState,
|
// Display loading indicator
|
||||||
onBack = goBack,
|
} else {
|
||||||
onScanned = onScanned,
|
Scan(
|
||||||
onOpenSettings = {
|
snackbarHostState = snackbarHostState,
|
||||||
runCatching {
|
onBack = goBack,
|
||||||
activity.startActivity(SettingsUtil.newSettingsIntent(activity.packageName))
|
onScanned = { result ->
|
||||||
}.onFailure {
|
|
||||||
// This case should not really happen, as the Settings app should be available on every
|
|
||||||
// Android device, but we need to handle it somehow.
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
snackbarHostState.showSnackbar(
|
val isAddressValid = !synchronizer.validateAddress(result).isNotValid
|
||||||
message = activity.getString(R.string.scan_settings_open_failed)
|
if (isAddressValid) {
|
||||||
)
|
onScanValid(result)
|
||||||
|
} else {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = activity.getString(R.string.scan_validation_invalid_address)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
onOpenSettings = {
|
||||||
onScanStateChanged = {}
|
runCatching {
|
||||||
)
|
activity.startActivity(SettingsUtil.newSettingsIntent(activity.packageName))
|
||||||
|
}.onFailure {
|
||||||
|
// This case should not really happen, as the Settings app should be available on every
|
||||||
|
// Android device, but we need to handle it somehow.
|
||||||
|
scope.launch {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = activity.getString(R.string.scan_settings_open_failed)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onScanStateChanged = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
package co.electriccoin.zcash.ui.screen.send
|
package co.electriccoin.zcash.ui.screen.send
|
||||||
|
|
||||||
|
import android.content.pm.PackageManager
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
|
@ -23,22 +24,29 @@ import co.electriccoin.zcash.ui.MainActivity
|
||||||
import co.electriccoin.zcash.ui.screen.home.model.spendableBalance
|
import co.electriccoin.zcash.ui.screen.home.model.spendableBalance
|
||||||
import co.electriccoin.zcash.ui.screen.home.viewmodel.WalletViewModel
|
import co.electriccoin.zcash.ui.screen.home.viewmodel.WalletViewModel
|
||||||
import co.electriccoin.zcash.ui.screen.send.ext.Saver
|
import co.electriccoin.zcash.ui.screen.send.ext.Saver
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.model.SendArgumentsWrapper
|
||||||
import co.electriccoin.zcash.ui.screen.send.model.SendStage
|
import co.electriccoin.zcash.ui.screen.send.model.SendStage
|
||||||
import co.electriccoin.zcash.ui.screen.send.view.Send
|
import co.electriccoin.zcash.ui.screen.send.view.Send
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun MainActivity.WrapSend(
|
internal fun MainActivity.WrapSend(
|
||||||
|
sendArgumentsWrapper: SendArgumentsWrapper?,
|
||||||
|
goToQrScanner: () -> Unit,
|
||||||
goBack: () -> Unit
|
goBack: () -> Unit
|
||||||
) {
|
) {
|
||||||
WrapSend(this, goBack)
|
WrapSend(this, sendArgumentsWrapper, goToQrScanner, goBack)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun WrapSend(
|
private fun WrapSend(
|
||||||
activity: ComponentActivity,
|
activity: ComponentActivity,
|
||||||
|
sendArgumentsWrapper: SendArgumentsWrapper?,
|
||||||
|
goToQrScanner: () -> Unit,
|
||||||
goBack: () -> Unit
|
goBack: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
val hasCameraFeature = activity.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
|
||||||
|
|
||||||
val walletViewModel by activity.viewModels<WalletViewModel>()
|
val walletViewModel by activity.viewModels<WalletViewModel>()
|
||||||
|
|
||||||
val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value
|
val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value
|
||||||
|
@ -47,16 +55,20 @@ private fun WrapSend(
|
||||||
|
|
||||||
val spendingKey = walletViewModel.spendingKey.collectAsStateWithLifecycle().value
|
val spendingKey = walletViewModel.spendingKey.collectAsStateWithLifecycle().value
|
||||||
|
|
||||||
WrapSend(synchronizer, spendableBalance, spendingKey, goBack)
|
WrapSend(sendArgumentsWrapper, synchronizer, spendableBalance, spendingKey, goToQrScanner, goBack, hasCameraFeature)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("LongParameterList")
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
@Composable
|
@Composable
|
||||||
internal fun WrapSend(
|
internal fun WrapSend(
|
||||||
|
sendArgumentsWrapper: SendArgumentsWrapper?,
|
||||||
synchronizer: Synchronizer?,
|
synchronizer: Synchronizer?,
|
||||||
spendableBalance: Zatoshi?,
|
spendableBalance: Zatoshi?,
|
||||||
spendingKey: UnifiedSpendingKey?,
|
spendingKey: UnifiedSpendingKey?,
|
||||||
goBack: () -> Unit
|
goToQrScanner: () -> Unit,
|
||||||
|
goBack: () -> Unit,
|
||||||
|
hasCameraFeature: Boolean
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
@ -88,6 +100,7 @@ internal fun WrapSend(
|
||||||
} else {
|
} else {
|
||||||
Send(
|
Send(
|
||||||
mySpendableBalance = spendableBalance,
|
mySpendableBalance = spendableBalance,
|
||||||
|
sendArgumentsWrapper = sendArgumentsWrapper,
|
||||||
sendStage = sendStage,
|
sendStage = sendStage,
|
||||||
onSendStageChange = setSendStage,
|
onSendStageChange = setSendStage,
|
||||||
zecSend = zecSend,
|
zecSend = zecSend,
|
||||||
|
@ -111,7 +124,9 @@ internal fun WrapSend(
|
||||||
// All other states of Pending transaction mean waiting for one of the states above
|
// All other states of Pending transaction mean waiting for one of the states above
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
onQrScannerOpen = goToQrScanner,
|
||||||
|
hasCameraFeature = hasCameraFeature
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package co.electriccoin.zcash.ui.screen.send.model
|
||||||
|
|
||||||
|
data class SendArgumentsWrapper(
|
||||||
|
val recipientAddress: String? = null,
|
||||||
|
val amount: String? = null,
|
||||||
|
val memo: String? = null
|
||||||
|
)
|
|
@ -12,6 +12,7 @@ import androidx.compose.foundation.text.KeyboardOptions
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowBack
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.outlined.QrCodeScanner
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
@ -50,6 +51,7 @@ import co.electriccoin.zcash.ui.design.theme.ZcashTheme.dimens
|
||||||
import co.electriccoin.zcash.ui.screen.send.ext.ABBREVIATION_INDEX
|
import co.electriccoin.zcash.ui.screen.send.ext.ABBREVIATION_INDEX
|
||||||
import co.electriccoin.zcash.ui.screen.send.ext.abbreviated
|
import co.electriccoin.zcash.ui.screen.send.ext.abbreviated
|
||||||
import co.electriccoin.zcash.ui.screen.send.ext.valueOrEmptyChar
|
import co.electriccoin.zcash.ui.screen.send.ext.valueOrEmptyChar
|
||||||
|
import co.electriccoin.zcash.ui.screen.send.model.SendArgumentsWrapper
|
||||||
import co.electriccoin.zcash.ui.screen.send.model.SendStage
|
import co.electriccoin.zcash.ui.screen.send.model.SendStage
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -59,12 +61,15 @@ fun PreviewSend() {
|
||||||
GradientSurface {
|
GradientSurface {
|
||||||
Send(
|
Send(
|
||||||
mySpendableBalance = ZatoshiFixture.new(),
|
mySpendableBalance = ZatoshiFixture.new(),
|
||||||
|
sendArgumentsWrapper = null,
|
||||||
sendStage = SendStage.Form,
|
sendStage = SendStage.Form,
|
||||||
onSendStageChange = {},
|
onSendStageChange = {},
|
||||||
zecSend = null,
|
zecSend = null,
|
||||||
onZecSendChange = {},
|
onZecSendChange = {},
|
||||||
onCreateAndSend = {},
|
onCreateAndSend = {},
|
||||||
onBack = {}
|
onQrScannerOpen = {},
|
||||||
|
onBack = {},
|
||||||
|
hasCameraFeature = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,12 +80,15 @@ fun PreviewSend() {
|
||||||
@Composable
|
@Composable
|
||||||
fun Send(
|
fun Send(
|
||||||
mySpendableBalance: Zatoshi,
|
mySpendableBalance: Zatoshi,
|
||||||
|
sendArgumentsWrapper: SendArgumentsWrapper?,
|
||||||
sendStage: SendStage,
|
sendStage: SendStage,
|
||||||
onSendStageChange: (SendStage) -> Unit,
|
onSendStageChange: (SendStage) -> Unit,
|
||||||
zecSend: ZecSend?,
|
zecSend: ZecSend?,
|
||||||
onZecSendChange: (ZecSend) -> Unit,
|
onZecSendChange: (ZecSend) -> Unit,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onCreateAndSend: (ZecSend) -> Unit
|
onCreateAndSend: (ZecSend) -> Unit,
|
||||||
|
onQrScannerOpen: () -> Unit,
|
||||||
|
hasCameraFeature: Boolean
|
||||||
) {
|
) {
|
||||||
Scaffold(topBar = {
|
Scaffold(topBar = {
|
||||||
SendTopAppBar(
|
SendTopAppBar(
|
||||||
|
@ -90,12 +98,15 @@ fun Send(
|
||||||
}) { paddingValues ->
|
}) { paddingValues ->
|
||||||
SendMainContent(
|
SendMainContent(
|
||||||
myBalance = mySpendableBalance,
|
myBalance = mySpendableBalance,
|
||||||
|
sendArgumentsWrapper = sendArgumentsWrapper,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
sendStage = sendStage,
|
sendStage = sendStage,
|
||||||
onSendStageChange = onSendStageChange,
|
onSendStageChange = onSendStageChange,
|
||||||
zecSend = zecSend,
|
zecSend = zecSend,
|
||||||
onZecSendChange = onZecSendChange,
|
onZecSendChange = onZecSendChange,
|
||||||
onSendSubmit = onCreateAndSend,
|
onSendSubmit = onCreateAndSend,
|
||||||
|
onQrScannerOpen = onQrScannerOpen,
|
||||||
|
hasCameraFeature = hasCameraFeature,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(
|
.padding(
|
||||||
top = paddingValues.calculateTopPadding() + dimens.spacingDefault,
|
top = paddingValues.calculateTopPadding() + dimens.spacingDefault,
|
||||||
|
@ -136,23 +147,29 @@ private fun SendTopAppBar(
|
||||||
@Composable
|
@Composable
|
||||||
private fun SendMainContent(
|
private fun SendMainContent(
|
||||||
myBalance: Zatoshi,
|
myBalance: Zatoshi,
|
||||||
|
sendArgumentsWrapper: SendArgumentsWrapper?,
|
||||||
zecSend: ZecSend?,
|
zecSend: ZecSend?,
|
||||||
onZecSendChange: (ZecSend) -> Unit,
|
onZecSendChange: (ZecSend) -> Unit,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
sendStage: SendStage,
|
sendStage: SendStage,
|
||||||
onSendStageChange: (SendStage) -> Unit,
|
onSendStageChange: (SendStage) -> Unit,
|
||||||
onSendSubmit: (ZecSend) -> Unit,
|
onSendSubmit: (ZecSend) -> Unit,
|
||||||
|
onQrScannerOpen: () -> Unit,
|
||||||
|
hasCameraFeature: Boolean,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
(sendStage == SendStage.Form || null == zecSend) -> {
|
(sendStage == SendStage.Form || null == zecSend) -> {
|
||||||
SendForm(
|
SendForm(
|
||||||
myBalance = myBalance,
|
myBalance = myBalance,
|
||||||
|
sendArgumentsWrapper = sendArgumentsWrapper,
|
||||||
previousZecSend = zecSend,
|
previousZecSend = zecSend,
|
||||||
onCreateZecSend = {
|
onCreateZecSend = {
|
||||||
onSendStageChange(SendStage.Confirmation)
|
onSendStageChange(SendStage.Confirmation)
|
||||||
onZecSendChange(it)
|
onZecSendChange(it)
|
||||||
},
|
},
|
||||||
|
onQrScannerOpen = onQrScannerOpen,
|
||||||
|
hasCameraFeature = hasCameraFeature,
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -192,12 +209,15 @@ private fun SendMainContent(
|
||||||
// TODO [#217]: Need to handle changing of Locale after user input, but before submitting the button.
|
// 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.
|
// TODO [#288]: TextField component can't do long-press backspace.
|
||||||
// TODO [#294]: DetektAll failed LongMethod
|
// TODO [#294]: DetektAll failed LongMethod
|
||||||
@Suppress("LongMethod")
|
@Suppress("LongMethod", "LongParameterList")
|
||||||
@Composable
|
@Composable
|
||||||
private fun SendForm(
|
private fun SendForm(
|
||||||
myBalance: Zatoshi,
|
myBalance: Zatoshi,
|
||||||
|
sendArgumentsWrapper: SendArgumentsWrapper?,
|
||||||
previousZecSend: ZecSend?,
|
previousZecSend: ZecSend?,
|
||||||
onCreateZecSend: (ZecSend) -> Unit,
|
onCreateZecSend: (ZecSend) -> Unit,
|
||||||
|
onQrScannerOpen: () -> Unit,
|
||||||
|
hasCameraFeature: Boolean,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
@ -218,6 +238,16 @@ private fun SendForm(
|
||||||
mutableStateOf<Set<ZecSendExt.ZecSendValidation.Invalid.ValidationError>>(emptySet())
|
mutableStateOf<Set<ZecSendExt.ZecSendValidation.Invalid.ValidationError>>(emptySet())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sendArgumentsWrapper?.recipientAddress != null) {
|
||||||
|
recipientAddressString = sendArgumentsWrapper.recipientAddress
|
||||||
|
}
|
||||||
|
if (sendArgumentsWrapper?.amount != null) {
|
||||||
|
amountZecString = sendArgumentsWrapper.amount
|
||||||
|
}
|
||||||
|
if (sendArgumentsWrapper?.memo != null) {
|
||||||
|
memoString = sendArgumentsWrapper.memo
|
||||||
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier
|
modifier
|
||||||
.fillMaxHeight()
|
.fillMaxHeight()
|
||||||
|
@ -236,6 +266,26 @@ private fun SendForm(
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(dimens.spacingLarge))
|
Spacer(modifier = Modifier.height(dimens.spacingLarge))
|
||||||
|
|
||||||
|
FormTextField(
|
||||||
|
value = recipientAddressString,
|
||||||
|
onValueChange = { recipientAddressString = it },
|
||||||
|
label = { Text(stringResource(id = R.string.send_to)) },
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
trailingIcon = if (hasCameraFeature) { {
|
||||||
|
IconButton(
|
||||||
|
onClick = onQrScannerOpen,
|
||||||
|
content = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.QrCodeScanner,
|
||||||
|
contentDescription = stringResource(R.string.send_scan_content_description)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} } else { null }
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(Modifier.size(dimens.spacingSmall))
|
||||||
|
|
||||||
FormTextField(
|
FormTextField(
|
||||||
value = amountZecString,
|
value = amountZecString,
|
||||||
onValueChange = { newValue ->
|
onValueChange = { newValue ->
|
||||||
|
@ -251,15 +301,6 @@ private fun SendForm(
|
||||||
|
|
||||||
Spacer(Modifier.size(dimens.spacingSmall))
|
Spacer(Modifier.size(dimens.spacingSmall))
|
||||||
|
|
||||||
FormTextField(
|
|
||||||
value = recipientAddressString,
|
|
||||||
onValueChange = { recipientAddressString = it },
|
|
||||||
label = { Text(stringResource(id = R.string.send_to)) },
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(Modifier.size(dimens.spacingSmall))
|
|
||||||
|
|
||||||
// TODO [#810]: Disable Memo UI field in case of Transparent address
|
// TODO [#810]: Disable Memo UI field in case of Transparent address
|
||||||
// TODO [#810]: https://github.com/zcash/secant-android-wallet/issues/810
|
// TODO [#810]: https://github.com/zcash/secant-android-wallet/issues/810
|
||||||
FormTextField(
|
FormTextField(
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
<string name="home_menu_about">About</string>
|
<string name="home_menu_about">About</string>
|
||||||
<string name="home_menu_support">Contact support</string>
|
<string name="home_menu_support">Contact support</string>
|
||||||
|
|
||||||
|
|
||||||
<string name="home_status_syncing_format" formatted="true">Syncing - <xliff:g id="synced_percent" example="50">%1$d</xliff:g>%%</string> <!-- double %% for escaping -->
|
<string name="home_status_syncing_format" formatted="true">Syncing - <xliff:g id="synced_percent" example="50">%1$d</xliff:g>%%</string> <!-- double %% for escaping -->
|
||||||
<string name="home_status_syncing_catchup">Syncing</string>
|
<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_amount_suffix" formatted="true"><xliff:g id="amount_prefix" example="123$">%1$s</xliff:g> so far</string>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<string name="scan_back_content_description">Back</string>
|
<string name="scan_back_content_description">Back</string>
|
||||||
<string name="scan_preview_content_description">Camera</string>
|
<string name="scan_preview_content_description">Camera</string>
|
||||||
|
|
||||||
<string name="scan_hint">We will validate any Zcash URI and take you to the appropriate action.</string>
|
<string name="scan_hint">You can scan any Zcash QR code.</string>
|
||||||
|
|
||||||
<string name="scan_settings_button">Enable camera permission</string>
|
<string name="scan_settings_button">Enable camera permission</string>
|
||||||
<string name="scan_settings_open_failed">Unable to launch Settings app.</string>
|
<string name="scan_settings_open_failed">Unable to launch Settings app.</string>
|
||||||
|
@ -15,4 +15,6 @@
|
||||||
<string name="scan_state_permission">Permission for camera is necessary.</string>
|
<string name="scan_state_permission">Permission for camera is necessary.</string>
|
||||||
<string name="scan_state_scanning">Scanning…</string>
|
<string name="scan_state_scanning">Scanning…</string>
|
||||||
<string name="scan_state_failed">Scanning failed.</string>
|
<string name="scan_state_failed">Scanning failed.</string>
|
||||||
|
|
||||||
|
<string name="scan_validation_invalid_address">Invalid Zcash address scanned.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in New Issue