diff --git a/docs/testing/manual_testing/QR Scan.md b/docs/testing/manual_testing/QR Scan.md
index 6eeeb706..0be524a9 100644
--- a/docs/testing/manual_testing/QR Scan.md
+++ b/docs/testing/manual_testing/QR Scan.md
@@ -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. 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. 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
\ No newline at end of file
diff --git a/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/TextField.kt b/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/TextField.kt
index 243b183d..de9d669e 100644
--- a/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/TextField.kt
+++ b/ui-design-lib/src/main/java/co/electriccoin/zcash/ui/design/component/TextField.kt
@@ -18,6 +18,8 @@ fun FormTextField(
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
label: @Composable (() -> Unit)? = null,
+ leadingIcon: @Composable (() -> Unit)? = null,
+ trailingIcon: @Composable (() -> Unit)? = null,
keyboardOptions: KeyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text),
colors: TextFieldColors = TextFieldDefaults.textFieldColors(
containerColor = Color.Transparent
@@ -29,6 +31,8 @@ fun FormTextField(
label = label,
keyboardOptions = keyboardOptions,
colors = colors,
- modifier = modifier
+ modifier = modifier,
+ leadingIcon = leadingIcon,
+ trailingIcon = trailingIcon
)
}
diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/fixture/SendArgumentsWrapperFixture.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/fixture/SendArgumentsWrapperFixture.kt
new file mode 100644
index 00000000..f489e150
--- /dev/null
+++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/fixture/SendArgumentsWrapperFixture.kt
@@ -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
+ )
+}
diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/send/ComposeContentTestRuleExt.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/send/ComposeContentTestRuleExt.kt
index 3ff867c6..03fe3902 100644
--- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/send/ComposeContentTestRuleExt.kt
+++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/send/ComposeContentTestRuleExt.kt
@@ -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() {
onNodeWithText(getStringResource(R.string.send_amount)).also {
val separators = MonetarySeparators.current()
diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/send/SendViewTestSetup.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/send/SendViewTestSetup.kt
index 89374da2..03026607 100644
--- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/send/SendViewTestSetup.kt
+++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/send/SendViewTestSetup.kt
@@ -9,6 +9,7 @@ import cash.z.ecc.android.sdk.model.ZecSend
import cash.z.ecc.sdk.fixture.ZatoshiFixture
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
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.view.Send
import kotlinx.coroutines.flow.MutableStateFlow
@@ -18,10 +19,13 @@ import java.util.concurrent.atomic.AtomicInteger
class SendViewTestSetup(
private val composeTestRule: ComposeContentTestRule,
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 onCreateCount = AtomicInteger(0)
+ private val onScannerCount = AtomicInteger(0)
val mutableActionExecuted = MutableStateFlow(false)
@Volatile
@@ -40,6 +44,11 @@ class SendViewTestSetup(
return onCreateCount.get()
}
+ fun getOnScannerCount(): Int {
+ composeTestRule.waitForIdle()
+ return onScannerCount.get()
+ }
+
fun getLastZecSend(): ZecSend? {
composeTestRule.waitForIdle()
return lastZecSend
@@ -82,6 +91,7 @@ class SendViewTestSetup(
Send(
mySpendableBalance = ZatoshiFixture.new(),
sendStage = sendStage,
+ sendArgumentsWrapper = initialSendArgumentWrapper,
onSendStageChange = setSendStage,
zecSend = zecSend,
onZecSendChange = setZecSend,
@@ -90,7 +100,11 @@ class SendViewTestSetup(
onCreateCount.incrementAndGet()
lastZecSend = it
mutableActionExecuted.update { true }
- }
+ },
+ onQrScannerOpen = {
+ onScannerCount.incrementAndGet()
+ },
+ hasCameraFeature = hasCameraFeature
)
}
}
diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/send/integration/SendViewIntegrationTest.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/send/integration/SendViewIntegrationTest.kt
index 6c3daaf9..129d1926 100644
--- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/send/integration/SendViewIntegrationTest.kt
+++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/send/integration/SendViewIntegrationTest.kt
@@ -1,6 +1,6 @@
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.createComposeRule
import androidx.compose.ui.test.onNodeWithText
@@ -51,10 +51,13 @@ class SendViewIntegrationTest {
restorationTester.setContent {
WrapSend(
+ sendArgumentsWrapper = null,
synchronizer = synchronizer,
spendableBalance = balance,
spendingKey = spendingKey,
- goBack = {}
+ goToQrScanner = {},
+ goBack = {},
+ hasCameraFeature = true
)
}
@@ -77,14 +80,24 @@ class SendViewIntegrationTest {
composeTestRule.clickBack()
composeTestRule.assertOnForm()
- // And check recreated form values too
- // We use that the assertTextContains searches in SemanticsProperties.EditableText too
- // Note also that we don't check the amount field value, as it's changed by validation mechanisms
+ // And check recreated form values too. 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 {
- it.assertTextContains(ZecSendFixture.ADDRESS)
+ it.assertTextEquals(
+ getStringResource(R.string.send_to),
+ ZecSendFixture.ADDRESS,
+ includeEditableText = true
+ )
}
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
+ )
}
}
}
diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/send/view/SendViewAndroidTest.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/send/view/SendViewAndroidTest.kt
index 6d27cd39..15c8e007 100644
--- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/send/view/SendViewAndroidTest.kt
+++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/send/view/SendViewAndroidTest.kt
@@ -31,7 +31,9 @@ class SendViewAndroidTest : UiTestPrerequisites() {
) = SendViewTestSetup(
composeTestRule,
sendStage,
- zecSend
+ zecSend,
+ null,
+ true
).apply {
setDefaultContent()
}
diff --git a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/send/view/SendViewTest.kt b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/send/view/SendViewTest.kt
index 46b24f78..8a2d602b 100644
--- a/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/send/view/SendViewTest.kt
+++ b/ui-lib/src/androidTest/java/co/electriccoin/zcash/ui/screen/send/view/SendViewTest.kt
@@ -1,6 +1,7 @@
package co.electriccoin.zcash.ui.screen.send.view
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.onNodeWithText
@@ -16,6 +17,7 @@ import cash.z.ecc.sdk.fixture.ZecRequestFixture
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.SendViewTestSetup
import co.electriccoin.zcash.ui.screen.send.assertOnConfirmation
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.clickConfirmation
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.setAmount
import co.electriccoin.zcash.ui.screen.send.setMemo
@@ -51,11 +55,15 @@ class SendViewTest : UiTestPrerequisites() {
private fun newTestSetup(
sendStage: SendStage = SendStage.Form,
- zecSend: ZecSend? = null
+ zecSend: ZecSend? = null,
+ sendArgumentsWrapper: SendArgumentsWrapper? = null,
+ hasCameraFeature: Boolean = true
) = SendViewTestSetup(
composeTestRule,
sendStage,
- zecSend
+ zecSend,
+ sendArgumentsWrapper,
+ hasCameraFeature
).apply {
setDefaultContent()
}
@@ -395,4 +403,86 @@ class SendViewTest : UiTestPrerequisites() {
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()
+ }
+ }
}
diff --git a/ui-lib/src/main/AndroidManifest.xml b/ui-lib/src/main/AndroidManifest.xml
index 28885cf7..b2100c91 100644
--- a/ui-lib/src/main/AndroidManifest.xml
+++ b/ui-lib/src/main/AndroidManifest.xml
@@ -3,6 +3,8 @@
+
+
+ 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(SEND_RECIPIENT_ADDRESS)
+ backStackEntry.savedStateHandle.remove(SEND_AMOUNT)
+ backStackEntry.savedStateHandle.remove(SEND_MEMO)
}
composable(SUPPORT) {
// Pop back stack won't be right if we deep link into support
@@ -97,11 +116,14 @@ internal fun MainActivity.Navigation() {
}
composable(SCAN) {
WrapScanValidator(
- onScanValid = {
- // TODO [#449] https://github.com/zcash/secant-android-wallet/issues/449
- navController.navigateJustOnce(SEND) {
- popUpTo(HOME) { inclusive = false }
+ onScanValid = { result ->
+ // At this point we only pass recipient address
+ navController.previousBackStackEntry?.savedStateHandle?.apply {
+ set(SEND_RECIPIENT_ADDRESS, result)
+ set(SEND_AMOUNT, null)
+ set(SEND_MEMO, null)
}
+ navController.popBackStackJustOnce(SCAN)
},
goBack = { navController.popBackStackJustOnce(SCAN) }
)
@@ -137,6 +159,12 @@ private fun NavHostController.popBackStackJustOnce(currentRouteToBePopped: Strin
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 {
const val HOME = "home"
diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/scan/AndroidScan.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/scan/AndroidScan.kt
index 16d8c8bd..2aed1c4b 100644
--- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/scan/AndroidScan.kt
+++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/scan/AndroidScan.kt
@@ -7,7 +7,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import androidx.lifecycle.lifecycleScope
import co.electriccoin.zcash.ui.MainActivity
import co.electriccoin.zcash.ui.R
import co.electriccoin.zcash.ui.screen.home.viewmodel.WalletViewModel
@@ -20,7 +19,7 @@ internal fun MainActivity.WrapScanValidator(
onScanValid: (address: String) -> Unit,
goBack: () -> Unit
) {
- WrapScanValidator(
+ WrapScan(
this,
onScanValid = onScanValid,
goBack = goBack
@@ -28,7 +27,7 @@ internal fun MainActivity.WrapScanValidator(
}
@Composable
-private fun WrapScanValidator(
+fun WrapScan(
activity: ComponentActivity,
onScanValid: (address: String) -> Unit,
goBack: () -> Unit
@@ -37,50 +36,41 @@ private fun WrapScanValidator(
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 scope = rememberCoroutineScope()
- Scan(
- snackbarHostState,
- onBack = goBack,
- onScanned = onScanned,
- onOpenSettings = {
- 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.
+ if (synchronizer == null) {
+ // Display loading indicator
+ } else {
+ Scan(
+ snackbarHostState = snackbarHostState,
+ onBack = goBack,
+ onScanned = { result ->
scope.launch {
- snackbarHostState.showSnackbar(
- message = activity.getString(R.string.scan_settings_open_failed)
- )
+ val isAddressValid = !synchronizer.validateAddress(result).isNotValid
+ if (isAddressValid) {
+ onScanValid(result)
+ } else {
+ snackbarHostState.showSnackbar(
+ message = activity.getString(R.string.scan_validation_invalid_address)
+ )
+ }
}
- }
- },
- onScanStateChanged = {}
- )
+ },
+ onOpenSettings = {
+ 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 = {}
+ )
+ }
}
diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/AndroidSend.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/AndroidSend.kt
index 410fb113..3092d4cc 100644
--- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/AndroidSend.kt
+++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/AndroidSend.kt
@@ -2,6 +2,7 @@
package co.electriccoin.zcash.ui.screen.send
+import android.content.pm.PackageManager
import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
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.viewmodel.WalletViewModel
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.view.Send
import kotlinx.coroutines.launch
@Composable
internal fun MainActivity.WrapSend(
+ sendArgumentsWrapper: SendArgumentsWrapper?,
+ goToQrScanner: () -> Unit,
goBack: () -> Unit
) {
- WrapSend(this, goBack)
+ WrapSend(this, sendArgumentsWrapper, goToQrScanner, goBack)
}
@Composable
private fun WrapSend(
activity: ComponentActivity,
+ sendArgumentsWrapper: SendArgumentsWrapper?,
+ goToQrScanner: () -> Unit,
goBack: () -> Unit
) {
+ val hasCameraFeature = activity.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
+
val walletViewModel by activity.viewModels()
val synchronizer = walletViewModel.synchronizer.collectAsStateWithLifecycle().value
@@ -47,16 +55,20 @@ private fun WrapSend(
val spendingKey = walletViewModel.spendingKey.collectAsStateWithLifecycle().value
- WrapSend(synchronizer, spendableBalance, spendingKey, goBack)
+ WrapSend(sendArgumentsWrapper, synchronizer, spendableBalance, spendingKey, goToQrScanner, goBack, hasCameraFeature)
}
+@Suppress("LongParameterList")
@VisibleForTesting
@Composable
internal fun WrapSend(
+ sendArgumentsWrapper: SendArgumentsWrapper?,
synchronizer: Synchronizer?,
spendableBalance: Zatoshi?,
spendingKey: UnifiedSpendingKey?,
- goBack: () -> Unit
+ goToQrScanner: () -> Unit,
+ goBack: () -> Unit,
+ hasCameraFeature: Boolean
) {
val scope = rememberCoroutineScope()
@@ -88,6 +100,7 @@ internal fun WrapSend(
} else {
Send(
mySpendableBalance = spendableBalance,
+ sendArgumentsWrapper = sendArgumentsWrapper,
sendStage = sendStage,
onSendStageChange = setSendStage,
zecSend = zecSend,
@@ -111,7 +124,9 @@ internal fun WrapSend(
// All other states of Pending transaction mean waiting for one of the states above
}
}
- }
+ },
+ onQrScannerOpen = goToQrScanner,
+ hasCameraFeature = hasCameraFeature
)
}
}
diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/model/SendArgumentsWrapper.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/model/SendArgumentsWrapper.kt
new file mode 100644
index 00000000..2a1471a2
--- /dev/null
+++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/model/SendArgumentsWrapper.kt
@@ -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
+)
diff --git a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/view/SendView.kt b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/view/SendView.kt
index 51f4b674..6ca4c0fa 100644
--- a/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/view/SendView.kt
+++ b/ui-lib/src/main/java/co/electriccoin/zcash/ui/screen/send/view/SendView.kt
@@ -12,6 +12,7 @@ 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
@@ -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.abbreviated
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
@Composable
@@ -59,12 +61,15 @@ fun PreviewSend() {
GradientSurface {
Send(
mySpendableBalance = ZatoshiFixture.new(),
+ sendArgumentsWrapper = null,
sendStage = SendStage.Form,
onSendStageChange = {},
zecSend = null,
onZecSendChange = {},
onCreateAndSend = {},
- onBack = {}
+ onQrScannerOpen = {},
+ onBack = {},
+ hasCameraFeature = true
)
}
}
@@ -75,12 +80,15 @@ fun PreviewSend() {
@Composable
fun Send(
mySpendableBalance: Zatoshi,
+ sendArgumentsWrapper: SendArgumentsWrapper?,
sendStage: SendStage,
onSendStageChange: (SendStage) -> Unit,
zecSend: ZecSend?,
onZecSendChange: (ZecSend) -> Unit,
onBack: () -> Unit,
- onCreateAndSend: (ZecSend) -> Unit
+ onCreateAndSend: (ZecSend) -> Unit,
+ onQrScannerOpen: () -> Unit,
+ hasCameraFeature: Boolean
) {
Scaffold(topBar = {
SendTopAppBar(
@@ -90,12 +98,15 @@ fun Send(
}) { paddingValues ->
SendMainContent(
myBalance = mySpendableBalance,
+ sendArgumentsWrapper = sendArgumentsWrapper,
onBack = onBack,
sendStage = sendStage,
onSendStageChange = onSendStageChange,
zecSend = zecSend,
onZecSendChange = onZecSendChange,
onSendSubmit = onCreateAndSend,
+ onQrScannerOpen = onQrScannerOpen,
+ hasCameraFeature = hasCameraFeature,
modifier = Modifier
.padding(
top = paddingValues.calculateTopPadding() + dimens.spacingDefault,
@@ -136,23 +147,29 @@ private fun SendTopAppBar(
@Composable
private fun SendMainContent(
myBalance: Zatoshi,
+ sendArgumentsWrapper: SendArgumentsWrapper?,
zecSend: ZecSend?,
onZecSendChange: (ZecSend) -> Unit,
onBack: () -> Unit,
sendStage: SendStage,
onSendStageChange: (SendStage) -> Unit,
onSendSubmit: (ZecSend) -> Unit,
+ onQrScannerOpen: () -> Unit,
+ hasCameraFeature: Boolean,
modifier: Modifier = Modifier
) {
when {
(sendStage == SendStage.Form || null == zecSend) -> {
SendForm(
myBalance = myBalance,
+ sendArgumentsWrapper = sendArgumentsWrapper,
previousZecSend = zecSend,
onCreateZecSend = {
onSendStageChange(SendStage.Confirmation)
onZecSendChange(it)
},
+ onQrScannerOpen = onQrScannerOpen,
+ hasCameraFeature = hasCameraFeature,
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 [#288]: TextField component can't do long-press backspace.
// TODO [#294]: DetektAll failed LongMethod
-@Suppress("LongMethod")
+@Suppress("LongMethod", "LongParameterList")
@Composable
private fun SendForm(
myBalance: Zatoshi,
+ sendArgumentsWrapper: SendArgumentsWrapper?,
previousZecSend: ZecSend?,
onCreateZecSend: (ZecSend) -> Unit,
+ onQrScannerOpen: () -> Unit,
+ hasCameraFeature: Boolean,
modifier: Modifier = Modifier
) {
val context = LocalContext.current
@@ -218,6 +238,16 @@ private fun SendForm(
mutableStateOf>(emptySet())
}
+ if (sendArgumentsWrapper?.recipientAddress != null) {
+ recipientAddressString = sendArgumentsWrapper.recipientAddress
+ }
+ if (sendArgumentsWrapper?.amount != null) {
+ amountZecString = sendArgumentsWrapper.amount
+ }
+ if (sendArgumentsWrapper?.memo != null) {
+ memoString = sendArgumentsWrapper.memo
+ }
+
Column(
modifier
.fillMaxHeight()
@@ -236,6 +266,26 @@ private fun SendForm(
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(
value = amountZecString,
onValueChange = { newValue ->
@@ -251,15 +301,6 @@ private fun SendForm(
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]: https://github.com/zcash/secant-android-wallet/issues/810
FormTextField(
diff --git a/ui-lib/src/main/res/ui/home/values/strings.xml b/ui-lib/src/main/res/ui/home/values/strings.xml
index c2088987..5668a79a 100644
--- a/ui-lib/src/main/res/ui/home/values/strings.xml
+++ b/ui-lib/src/main/res/ui/home/values/strings.xml
@@ -8,7 +8,6 @@
About
Contact support
-
Syncing - %1$d%%
Syncing
%1$s so far
diff --git a/ui-lib/src/main/res/ui/scan/values/strings.xml b/ui-lib/src/main/res/ui/scan/values/strings.xml
index 74fa91cd..0733b45b 100644
--- a/ui-lib/src/main/res/ui/scan/values/strings.xml
+++ b/ui-lib/src/main/res/ui/scan/values/strings.xml
@@ -4,7 +4,7 @@
Back
Camera
- We will validate any Zcash URI and take you to the appropriate action.
+ You can scan any Zcash QR code.
Enable camera permission
Unable to launch Settings app.
@@ -15,4 +15,6 @@
Permission for camera is necessary.
Scanning…
Scanning failed.
+
+ Invalid Zcash address scanned.