[#1145] Receive screen
- Closes #1145 - Closes #1057 - Closes #1088 - Closes #1154 - Closes #1155 - Closes #1185
This commit is contained in:
parent
a276cb41e0
commit
6519df7539
|
@ -29,7 +29,7 @@
|
|||
tools:targetApi="29" />
|
||||
|
||||
<provider
|
||||
android:name="co.electriccoin.zcash.ui.screen.exportdata.util.ShareFileProvider"
|
||||
android:name="co.electriccoin.zcash.global.ShareFileProvider"
|
||||
android:authorities="co.electriccoin.zcash.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
android:label="@string/app_name">
|
||||
|
||||
<provider
|
||||
android:name="co.electriccoin.zcash.ui.screen.exportdata.util.ShareFileProvider"
|
||||
android:name="co.electriccoin.zcash.global.ShareFileProvider"
|
||||
android:authorities="co.electriccoin.zcash.debug.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true"
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
android:label="@string/app_name">
|
||||
|
||||
<provider
|
||||
android:name="co.electriccoin.zcash.ui.screen.exportdata.util.ShareFileProvider"
|
||||
android:name="co.electriccoin.zcash.global.ShareFileProvider"
|
||||
android:authorities="co.electriccoin.zcash.provider.testnet"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true"
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
android:label="@string/app_name">
|
||||
|
||||
<provider
|
||||
android:name="co.electriccoin.zcash.ui.screen.exportdata.util.ShareFileProvider"
|
||||
android:name="co.electriccoin.zcash.global.ShareFileProvider"
|
||||
android:authorities="co.electriccoin.zcash.debug.provider.testnet"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true"
|
||||
|
|
|
@ -188,7 +188,7 @@ ZCASH_BIP39_VERSION=1.0.7
|
|||
ZXING_VERSION=3.5.2
|
||||
|
||||
# WARNING: Ensure a non-snapshot version is used before releasing to production.
|
||||
ZCASH_SDK_VERSION=2.0.4-SNAPSHOT
|
||||
ZCASH_SDK_VERSION=2.0.4
|
||||
|
||||
# Toolchain is the Java version used to build the application, which is separate from the
|
||||
# Java version used to run the application.
|
||||
|
|
|
@ -3,10 +3,20 @@
|
|||
package co.electriccoin.zcash.spackle
|
||||
|
||||
import android.content.Context
|
||||
import co.electriccoin.zcash.spackle.io.mkdirsSuspend
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
// TODO [#1182]: Cover ContextExt with tests
|
||||
// TODO [#1182]: https://github.com/Electric-Coin-Company/zashi-android/issues/1182
|
||||
|
||||
suspend fun Context.getExternalFilesDirSuspend(type: String?) =
|
||||
withContext(Dispatchers.IO) {
|
||||
getExternalFilesDir(type)
|
||||
}
|
||||
|
||||
suspend fun Context.getInternalCacheDirSuspend(subDirectory: String?): File =
|
||||
withContext(Dispatchers.IO) {
|
||||
(subDirectory?.let { File(cacheDir, subDirectory) } ?: cacheDir).apply { mkdirsSuspend() }
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ suspend fun File.renameToSuspend(destination: File) =
|
|||
renameTo(destination)
|
||||
}
|
||||
|
||||
suspend fun File.listFilesSuspend() =
|
||||
suspend fun File.listFilesSuspend(): Array<out File>? =
|
||||
withContext(Dispatchers.IO) {
|
||||
listFiles()
|
||||
}
|
||||
|
|
|
@ -5,25 +5,47 @@ package co.electriccoin.zcash.ui.design.component
|
|||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.basicMarquee
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.AccountBox
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import co.electriccoin.zcash.ui.design.R
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun TextComposablePreview() {
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
GradientSurface {
|
||||
Column {
|
||||
Reference(text = "Test reference text", onClick = {})
|
||||
Reference(text = "User account", imageVector = Icons.Outlined.AccountBox, onClick = {})
|
||||
// Preview the rest of the composable
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Header(
|
||||
text: String,
|
||||
|
@ -36,7 +58,23 @@ fun Header(
|
|||
color = color,
|
||||
textAlign = textAlign,
|
||||
modifier = modifier,
|
||||
style = MaterialTheme.typography.headlineLarge,
|
||||
style = ZcashTheme.typography.secondary.headlineLarge,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SubHeader(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
textAlign: TextAlign = TextAlign.Start,
|
||||
color: Color = ZcashTheme.colors.onBackgroundHeader,
|
||||
) {
|
||||
Text(
|
||||
text = text,
|
||||
color = color,
|
||||
textAlign = textAlign,
|
||||
modifier = modifier,
|
||||
style = ZcashTheme.typography.secondary.headlineSmall,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -171,33 +209,46 @@ fun ListHeader(
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
fun Reference(
|
||||
text: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
textAlign: TextAlign = TextAlign.Start,
|
||||
onClick: () -> Unit
|
||||
textAlign: TextAlign = TextAlign.Center,
|
||||
imageVector: ImageVector? = null,
|
||||
imageContentDescription: String? = null
|
||||
) {
|
||||
Box(
|
||||
Row(
|
||||
modifier =
|
||||
Modifier
|
||||
.wrapContentSize()
|
||||
.clip(RoundedCornerShape(ZcashTheme.dimens.topAppBarActionRippleCorner))
|
||||
.clickable { onClick() }
|
||||
.then(modifier)
|
||||
.padding(all = ZcashTheme.dimens.spacingDefault)
|
||||
.then(modifier),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
imageVector?.let {
|
||||
Icon(
|
||||
imageVector = imageVector,
|
||||
contentDescription = imageContentDescription
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.padding(ZcashTheme.dimens.spacingTiny))
|
||||
Text(
|
||||
text = text,
|
||||
textAlign = TextAlign.Center,
|
||||
style =
|
||||
MaterialTheme.typography.bodyLarge
|
||||
ZcashTheme.typography.primary.bodyLarge
|
||||
.merge(
|
||||
TextStyle(
|
||||
color = ZcashTheme.colors.reference,
|
||||
textAlign = textAlign,
|
||||
textDecoration = TextDecoration.Underline
|
||||
textDecoration = TextDecoration.Underline,
|
||||
fontWeight = FontWeight.SemiBold
|
||||
)
|
||||
)
|
||||
),
|
||||
modifier = Modifier.padding(all = ZcashTheme.dimens.spacingDefault)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -220,7 +271,10 @@ fun HeaderWithZecIcon(
|
|||
style = ZcashTheme.extendedTypography.zecBalance,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
maxLines = 1,
|
||||
modifier = Modifier.basicMarquee().then(modifier)
|
||||
modifier =
|
||||
Modifier
|
||||
.basicMarquee()
|
||||
.then(modifier)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -19,13 +19,10 @@ data class ExtendedColors(
|
|||
val progressBackground: Color,
|
||||
val chipIndex: Color,
|
||||
val textFieldHint: Color,
|
||||
val textDescription: Color,
|
||||
val layoutStroke: Color,
|
||||
val overlay: Color,
|
||||
val highlight: Color,
|
||||
val addressHighlightBorder: Color,
|
||||
val addressHighlightUnified: Color,
|
||||
val addressHighlightSapling: Color,
|
||||
val addressHighlightTransparent: Color,
|
||||
val dangerous: Color,
|
||||
val onDangerous: Color,
|
||||
val reference: Color,
|
||||
|
|
|
@ -27,6 +27,8 @@ internal object Dark {
|
|||
val textCaption = Color(0xFFFFFFFF)
|
||||
val textChipIndex = Color(0xFFFFB900)
|
||||
val textFieldHint = Color(0xFFB7B7B7)
|
||||
val textDescription = Color(0xFF777777)
|
||||
|
||||
val layoutStroke = Color(0xFFFFFFFF)
|
||||
|
||||
val primaryButton = Color(0xFFFFFFFF)
|
||||
|
@ -90,10 +92,10 @@ internal object Light {
|
|||
val textCaption = Color(0xFF000000)
|
||||
val textChipIndex = Color(0xFFEE8592)
|
||||
val textFieldHint = Color(0xFFB7B7B7)
|
||||
val textDescription = Color(0xFF777777)
|
||||
|
||||
val layoutStroke = Color(0xFF000000)
|
||||
|
||||
// TODO [#159]: The button colors are wrong for light
|
||||
// TODO [#159]: https://github.com/Electric-Coin-Company/zashi-android/issues/159
|
||||
val primaryButton = Color(0xFF000000)
|
||||
val primaryButtonPressed = Color(0xFF000000)
|
||||
val primaryButtonDisabled = Color(0xFF000000)
|
||||
|
@ -118,13 +120,6 @@ internal object Light {
|
|||
val overlay = Color(0x22000000)
|
||||
val highlight = Color(0xFFFFD800)
|
||||
|
||||
// TODO [#159]: The colors are wrong for light theme
|
||||
// TODO [#159]: https://github.com/Electric-Coin-Company/zashi-android/issues/159
|
||||
val addressHighlightBorder = Color(0xFF525252)
|
||||
val addressHighlightUnified = Color(0xFFFFD800)
|
||||
val addressHighlightSapling = Color(0xFF1BBFF6)
|
||||
val addressHighlightTransparent = Color(0xFF97999A)
|
||||
|
||||
val dangerous = Color(0xFFEC0008)
|
||||
val onDangerous = Color(0xFFFFFFFF)
|
||||
|
||||
|
@ -179,13 +174,10 @@ internal val DarkExtendedColorPalette =
|
|||
progressBackground = Dark.progressBackground,
|
||||
chipIndex = Dark.textChipIndex,
|
||||
textFieldHint = Dark.textFieldHint,
|
||||
textDescription = Dark.textDescription,
|
||||
layoutStroke = Dark.layoutStroke,
|
||||
overlay = Dark.overlay,
|
||||
highlight = Dark.highlight,
|
||||
addressHighlightBorder = Dark.addressHighlightBorder,
|
||||
addressHighlightUnified = Dark.addressHighlightUnified,
|
||||
addressHighlightSapling = Dark.addressHighlightSapling,
|
||||
addressHighlightTransparent = Dark.addressHighlightTransparent,
|
||||
dangerous = Dark.dangerous,
|
||||
onDangerous = Dark.onDangerous,
|
||||
disabledButtonTextColor = Dark.disabledButtonTextColor,
|
||||
|
@ -213,13 +205,10 @@ internal val LightExtendedColorPalette =
|
|||
progressBackground = Light.progressBackground,
|
||||
chipIndex = Light.textChipIndex,
|
||||
textFieldHint = Light.textFieldHint,
|
||||
textDescription = Light.textDescription,
|
||||
layoutStroke = Light.layoutStroke,
|
||||
overlay = Light.overlay,
|
||||
highlight = Light.highlight,
|
||||
addressHighlightBorder = Light.addressHighlightBorder,
|
||||
addressHighlightUnified = Light.addressHighlightUnified,
|
||||
addressHighlightSapling = Light.addressHighlightSapling,
|
||||
addressHighlightTransparent = Light.addressHighlightTransparent,
|
||||
dangerous = Light.dangerous,
|
||||
onDangerous = Light.onDangerous,
|
||||
disabledButtonTextColor = Light.disabledButtonTextColor,
|
||||
|
@ -249,13 +238,10 @@ internal val LocalExtendedColors =
|
|||
progressBackground = Color.Unspecified,
|
||||
chipIndex = Color.Unspecified,
|
||||
textFieldHint = Color.Unspecified,
|
||||
textDescription = Color.Unspecified,
|
||||
layoutStroke = Color.Unspecified,
|
||||
overlay = Color.Unspecified,
|
||||
highlight = Color.Unspecified,
|
||||
addressHighlightBorder = Color.Unspecified,
|
||||
addressHighlightUnified = Color.Unspecified,
|
||||
addressHighlightSapling = Color.Unspecified,
|
||||
addressHighlightTransparent = Color.Unspecified,
|
||||
dangerous = Color.Unspecified,
|
||||
onDangerous = Color.Unspecified,
|
||||
disabledButtonTextColor = Color.Unspecified,
|
||||
|
|
|
@ -107,7 +107,7 @@ internal val SecondaryTypography =
|
|||
headlineSmall =
|
||||
TextStyle(
|
||||
fontFamily = ArchivoFontFamily,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
fontSize = 20.sp,
|
||||
textAlign = TextAlign.Center
|
||||
),
|
||||
|
|
|
@ -1,185 +0,0 @@
|
|||
package co.electriccoin.zcash.ui.screen.address.view
|
||||
|
||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.test.filters.MediumTest
|
||||
import cash.z.ecc.android.sdk.fixture.WalletAddressesFixture
|
||||
import cash.z.ecc.android.sdk.model.WalletAddresses
|
||||
import co.electriccoin.zcash.test.UiTestPrerequisites
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.screen.address.WalletAddressesTag
|
||||
import co.electriccoin.zcash.ui.test.getStringResource
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
class WalletAddressViewTest : UiTestPrerequisites() {
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun initial_screen_setup() =
|
||||
runTest {
|
||||
val walletAddresses = WalletAddressesFixture.new()
|
||||
newTestSetup(walletAddresses)
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.wallet_address_unified)).also {
|
||||
it.assertExists()
|
||||
}
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.wallet_address_sapling)).also {
|
||||
it.assertExists()
|
||||
}
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.wallet_address_transparent)).also {
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(walletAddresses.unified.address).also {
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(walletAddresses.sapling.address).also {
|
||||
it.assertDoesNotExist()
|
||||
}
|
||||
composeTestRule.onNodeWithText(walletAddresses.transparent.address).also {
|
||||
it.assertDoesNotExist()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun unified_collapses() =
|
||||
runTest {
|
||||
val walletAddresses = WalletAddressesFixture.new()
|
||||
newTestSetup(walletAddresses)
|
||||
|
||||
composeTestRule.onNodeWithText(walletAddresses.unified.address).also {
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.wallet_address_unified)).also {
|
||||
it.assertExists()
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(walletAddresses.unified.address).also {
|
||||
it.assertDoesNotExist()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun sapling_expands() =
|
||||
runTest {
|
||||
val walletAddresses = WalletAddressesFixture.new()
|
||||
newTestSetup(walletAddresses)
|
||||
|
||||
composeTestRule.onNodeWithText(walletAddresses.sapling.address).also {
|
||||
it.assertDoesNotExist()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.wallet_address_sapling)).also {
|
||||
it.assertExists()
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(walletAddresses.sapling.address).also {
|
||||
it.assertExists()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun transparent_expands() =
|
||||
runTest {
|
||||
val walletAddresses = WalletAddressesFixture.new()
|
||||
newTestSetup(walletAddresses)
|
||||
|
||||
composeTestRule.onNodeWithText(walletAddresses.transparent.address).also {
|
||||
it.assertDoesNotExist()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(getStringResource(R.string.wallet_address_transparent)).also {
|
||||
it.assertExists()
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
composeTestRule.onNodeWithText(walletAddresses.transparent.address).also {
|
||||
it.assertExists()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun back_clicked() =
|
||||
runTest {
|
||||
val testSetup = newTestSetup(WalletAddressesFixture.new())
|
||||
|
||||
assertEquals(0, testSetup.getOnBackCount())
|
||||
|
||||
composeTestRule.onNodeWithContentDescription(
|
||||
getStringResource(R.string.wallet_address_back_content_description)
|
||||
).also {
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
assertEquals(1, testSetup.getOnBackCount())
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun copy_to_clipboard_clicked() =
|
||||
runTest {
|
||||
val testSetup = newTestSetup(WalletAddressesFixture.new())
|
||||
|
||||
assertEquals(0, testSetup.getOnCopyToClipboardCount())
|
||||
|
||||
composeTestRule.onNodeWithTag(
|
||||
WalletAddressesTag.WALLET_ADDRESS
|
||||
).also {
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
assertEquals(1, testSetup.getOnCopyToClipboardCount())
|
||||
}
|
||||
|
||||
private fun newTestSetup(initialState: WalletAddresses) = TestSetup(composeTestRule, initialState)
|
||||
|
||||
private class TestSetup(private val composeTestRule: ComposeContentTestRule, initialState: WalletAddresses) {
|
||||
private val onBackCount = AtomicInteger(0)
|
||||
private val onCopyToClipboardCount = AtomicInteger(0)
|
||||
|
||||
fun getOnBackCount(): Int {
|
||||
composeTestRule.waitForIdle()
|
||||
return onBackCount.get()
|
||||
}
|
||||
|
||||
fun getOnCopyToClipboardCount(): Int {
|
||||
composeTestRule.waitForIdle()
|
||||
return onCopyToClipboardCount.get()
|
||||
}
|
||||
|
||||
init {
|
||||
composeTestRule.setContent {
|
||||
ZcashTheme {
|
||||
WalletAddresses(
|
||||
walletAddresses = initialState,
|
||||
onCopyToClipboard = {
|
||||
onCopyToClipboardCount.incrementAndGet()
|
||||
},
|
||||
onBack = {
|
||||
onBackCount.incrementAndGet()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import android.content.Intent
|
|||
import androidx.test.filters.SmallTest
|
||||
import co.electriccoin.zcash.ui.fixture.VersionInfoFixture
|
||||
import co.electriccoin.zcash.ui.test.getAppContext
|
||||
import co.electriccoin.zcash.ui.util.FileShareUtil
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import kotlin.io.path.pathString
|
||||
|
@ -26,6 +27,7 @@ class FileShareUtilTest {
|
|||
FileShareUtil.newShareContentIntent(
|
||||
context = getAppContext(),
|
||||
dataFilePath = tempFilePath.pathString,
|
||||
fileType = FileShareUtil.ZASHI_INTERNAL_DATA_MIME_TYPE,
|
||||
versionInfo = VersionInfoFixture.new()
|
||||
)
|
||||
assertEquals(intent.action, Intent.ACTION_VIEW)
|
||||
|
|
|
@ -2,9 +2,11 @@ package co.electriccoin.zcash.ui.screen.receive.view
|
|||
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.test.filters.MediumTest
|
||||
import cash.z.ecc.android.sdk.fixture.WalletAddressFixture
|
||||
import cash.z.ecc.android.sdk.model.WalletAddress
|
||||
import cash.z.ecc.android.sdk.fixture.WalletAddressesFixture
|
||||
import cash.z.ecc.android.sdk.model.WalletAddresses
|
||||
import co.electriccoin.zcash.test.UiTestPrerequisites
|
||||
import co.electriccoin.zcash.ui.common.model.VersionInfo
|
||||
import co.electriccoin.zcash.ui.fixture.VersionInfoFixture
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -18,7 +20,12 @@ class ReceiveViewScreenBrightnessTest : UiTestPrerequisites() {
|
|||
@MediumTest
|
||||
fun testBrightnessDefaultState() =
|
||||
runTest {
|
||||
val testSetup = newTestSetup(WalletAddressFixture.unified())
|
||||
// Using isDebuggable flag to have brightness toggle in the UI
|
||||
val testSetup =
|
||||
newTestSetup(
|
||||
WalletAddressesFixture.new(),
|
||||
VersionInfoFixture.new(isDebuggable = true)
|
||||
)
|
||||
|
||||
assertEquals(0, testSetup.getScreenBrightnessCount())
|
||||
}
|
||||
|
@ -27,7 +34,12 @@ class ReceiveViewScreenBrightnessTest : UiTestPrerequisites() {
|
|||
@MediumTest
|
||||
fun testBrightnessOnState() =
|
||||
runTest {
|
||||
val testSetup = newTestSetup(WalletAddressFixture.unified())
|
||||
// Using isDebuggable flag to have brightness toggle in the UI
|
||||
val testSetup =
|
||||
newTestSetup(
|
||||
WalletAddressesFixture.new(),
|
||||
VersionInfoFixture.new(isDebuggable = true)
|
||||
)
|
||||
|
||||
assertEquals(false, testSetup.getOnAdjustBrightness())
|
||||
assertEquals(0, testSetup.getScreenBrightnessCount())
|
||||
|
@ -38,5 +50,8 @@ class ReceiveViewScreenBrightnessTest : UiTestPrerequisites() {
|
|||
assertEquals(1, testSetup.getScreenBrightnessCount())
|
||||
}
|
||||
|
||||
private fun newTestSetup(walletAddress: WalletAddress) = ReceiveViewTestSetup(composeTestRule, walletAddress)
|
||||
private fun newTestSetup(
|
||||
walletAddresses: WalletAddresses,
|
||||
versionInfo: VersionInfo
|
||||
) = ReceiveViewTestSetup(composeTestRule, walletAddresses, versionInfo)
|
||||
}
|
||||
|
|
|
@ -2,9 +2,11 @@ package co.electriccoin.zcash.ui.screen.receive.view
|
|||
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.test.filters.MediumTest
|
||||
import cash.z.ecc.android.sdk.fixture.WalletAddressFixture
|
||||
import cash.z.ecc.android.sdk.model.WalletAddress
|
||||
import cash.z.ecc.android.sdk.fixture.WalletAddressesFixture
|
||||
import cash.z.ecc.android.sdk.model.WalletAddresses
|
||||
import co.electriccoin.zcash.test.UiTestPrerequisites
|
||||
import co.electriccoin.zcash.ui.common.model.VersionInfo
|
||||
import co.electriccoin.zcash.ui.fixture.VersionInfoFixture
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
@ -18,7 +20,11 @@ class ReceiveViewScreenTimeoutTest : UiTestPrerequisites() {
|
|||
@MediumTest
|
||||
fun testTimeoutDefaultState() =
|
||||
runTest {
|
||||
val testSetup = newTestSetup(WalletAddressFixture.unified())
|
||||
val testSetup =
|
||||
newTestSetup(
|
||||
WalletAddressesFixture.new(),
|
||||
VersionInfoFixture.new()
|
||||
)
|
||||
|
||||
assertEquals(0, testSetup.getScreenTimeoutCount())
|
||||
}
|
||||
|
@ -27,7 +33,12 @@ class ReceiveViewScreenTimeoutTest : UiTestPrerequisites() {
|
|||
@MediumTest
|
||||
fun testTimeoutOnState() =
|
||||
runTest {
|
||||
val testSetup = newTestSetup(WalletAddressFixture.unified())
|
||||
// Using isDebuggable flag to have brightness toggle in the UI
|
||||
val testSetup =
|
||||
newTestSetup(
|
||||
WalletAddressesFixture.new(),
|
||||
VersionInfoFixture.new(isDebuggable = true)
|
||||
)
|
||||
|
||||
assertEquals(false, testSetup.getOnAdjustBrightness())
|
||||
assertEquals(0, testSetup.getScreenTimeoutCount())
|
||||
|
@ -38,5 +49,8 @@ class ReceiveViewScreenTimeoutTest : UiTestPrerequisites() {
|
|||
assertEquals(1, testSetup.getScreenTimeoutCount())
|
||||
}
|
||||
|
||||
private fun newTestSetup(walletAddress: WalletAddress) = ReceiveViewTestSetup(composeTestRule, walletAddress)
|
||||
private fun newTestSetup(
|
||||
walletAddresses: WalletAddresses,
|
||||
versionInfo: VersionInfo
|
||||
) = ReceiveViewTestSetup(composeTestRule, walletAddresses, versionInfo)
|
||||
}
|
||||
|
|
|
@ -5,8 +5,8 @@ import androidx.compose.ui.test.onNodeWithContentDescription
|
|||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.test.filters.MediumTest
|
||||
import cash.z.ecc.android.sdk.fixture.WalletAddressFixture
|
||||
import cash.z.ecc.android.sdk.model.WalletAddress
|
||||
import cash.z.ecc.android.sdk.fixture.WalletAddressesFixture
|
||||
import cash.z.ecc.android.sdk.model.WalletAddresses
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.test.getStringResource
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
@ -14,6 +14,9 @@ import org.junit.Assert.assertEquals
|
|||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
// TODO [#1184]: Improve ReceiveScreen UI tests
|
||||
// TODO [#1184]: https://github.com/Electric-Coin-Company/zashi-android/issues/1184
|
||||
|
||||
/*
|
||||
* Note: It is difficult to test the QR code from automated tests. There is a manual test case
|
||||
* for that currently. A future enhancement could take a screenshot and try to analyze the
|
||||
|
@ -27,11 +30,11 @@ class ReceiveViewTest {
|
|||
@MediumTest
|
||||
fun setup() =
|
||||
runTest {
|
||||
val walletAddress = WalletAddressFixture.unified()
|
||||
newTestSetup(walletAddress)
|
||||
val walletAddresses = WalletAddressesFixture.new()
|
||||
newTestSetup(walletAddresses)
|
||||
|
||||
// Enable substring for ellipsizing
|
||||
composeTestRule.onNodeWithText(walletAddress.address, substring = true).also {
|
||||
composeTestRule.onNodeWithText(walletAddresses.unified.address, substring = true).also {
|
||||
it.assertExists()
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +43,7 @@ class ReceiveViewTest {
|
|||
@MediumTest
|
||||
fun click_settings_test() =
|
||||
runTest {
|
||||
val testSetup = newTestSetup(WalletAddressFixture.unified())
|
||||
val testSetup = newTestSetup(WalletAddressesFixture.new())
|
||||
|
||||
assertEquals(0, testSetup.getOnSettingsCount())
|
||||
|
||||
|
@ -53,23 +56,5 @@ class ReceiveViewTest {
|
|||
assertEquals(1, testSetup.getOnSettingsCount())
|
||||
}
|
||||
|
||||
@Test
|
||||
@MediumTest
|
||||
fun address_details() =
|
||||
runTest {
|
||||
val testSetup = newTestSetup(WalletAddressFixture.unified())
|
||||
|
||||
assertEquals(0, testSetup.getOnAddressDetailsCount())
|
||||
|
||||
composeTestRule.onNodeWithText(
|
||||
text = getStringResource(R.string.receive_see_address_details),
|
||||
ignoreCase = true
|
||||
).also {
|
||||
it.performClick()
|
||||
}
|
||||
|
||||
assertEquals(1, testSetup.getOnAddressDetailsCount())
|
||||
}
|
||||
|
||||
private fun newTestSetup(walletAddress: WalletAddress) = ReceiveViewTestSetup(composeTestRule, walletAddress)
|
||||
private fun newTestSetup(walletAddresses: WalletAddresses) = ReceiveViewTestSetup(composeTestRule, walletAddresses)
|
||||
}
|
||||
|
|
|
@ -1,23 +1,27 @@
|
|||
package co.electriccoin.zcash.ui.screen.receive.view
|
||||
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.test.junit4.ComposeContentTestRule
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.performClick
|
||||
import cash.z.ecc.android.sdk.model.WalletAddress
|
||||
import cash.z.ecc.android.sdk.model.WalletAddresses
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.LocalScreenBrightness
|
||||
import co.electriccoin.zcash.ui.common.LocalScreenTimeout
|
||||
import co.electriccoin.zcash.ui.common.ScreenBrightness
|
||||
import co.electriccoin.zcash.ui.common.ScreenTimeout
|
||||
import co.electriccoin.zcash.ui.common.model.VersionInfo
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.fixture.VersionInfoFixture
|
||||
import co.electriccoin.zcash.ui.test.getStringResource
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
class ReceiveViewTestSetup(
|
||||
private val composeTestRule: ComposeContentTestRule,
|
||||
walletAddress: WalletAddress
|
||||
walletAddresses: WalletAddresses,
|
||||
versionInfo: VersionInfo = VersionInfoFixture.new()
|
||||
) {
|
||||
private val onSettingsCount = AtomicInteger(0)
|
||||
private val onAddressDetailsCount = AtomicInteger(0)
|
||||
|
@ -53,16 +57,17 @@ class ReceiveViewTestSetup(
|
|||
ZcashTheme {
|
||||
ZcashTheme {
|
||||
Receive(
|
||||
walletAddress,
|
||||
walletAddress = walletAddresses,
|
||||
snackbarHostState = SnackbarHostState(),
|
||||
onSettings = {
|
||||
onSettingsCount.getAndIncrement()
|
||||
},
|
||||
onAddressDetails = {
|
||||
onAddressDetailsCount.getAndIncrement()
|
||||
},
|
||||
onAdjustBrightness = {
|
||||
onAdjustBrightness.getAndSet(it)
|
||||
},
|
||||
onAddrCopyToClipboard = {},
|
||||
onQrImageShare = {},
|
||||
versionInfo = versionInfo
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package co.electriccoin.zcash.ui.screen.exportdata.util
|
||||
package co.electriccoin.zcash.global
|
||||
|
||||
import androidx.core.content.FileProvider
|
||||
import co.electriccoin.zcash.ui.R
|
|
@ -20,11 +20,9 @@ import co.electriccoin.zcash.ui.NavigationTargets.SCAN
|
|||
import co.electriccoin.zcash.ui.NavigationTargets.SEED_RECOVERY
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.SETTINGS
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.SUPPORT
|
||||
import co.electriccoin.zcash.ui.NavigationTargets.WALLET_ADDRESS_DETAILS
|
||||
import co.electriccoin.zcash.ui.configuration.ConfigurationEntries
|
||||
import co.electriccoin.zcash.ui.configuration.RemoteConfig
|
||||
import co.electriccoin.zcash.ui.screen.about.WrapAbout
|
||||
import co.electriccoin.zcash.ui.screen.address.WrapWalletAddresses
|
||||
import co.electriccoin.zcash.ui.screen.exportdata.WrapExportPrivateData
|
||||
import co.electriccoin.zcash.ui.screen.history.WrapHistory
|
||||
import co.electriccoin.zcash.ui.screen.home.WrapHome
|
||||
|
@ -51,7 +49,6 @@ internal fun MainActivity.Navigation() {
|
|||
onPageChange = {
|
||||
homeViewModel.screenIndex.value = it
|
||||
},
|
||||
goAddressDetails = { navController.navigateJustOnce(WALLET_ADDRESS_DETAILS) },
|
||||
goBack = { finish() },
|
||||
goHistory = { navController.navigateJustOnce(HISTORY) },
|
||||
goSettings = { navController.navigateJustOnce(SETTINGS) },
|
||||
|
@ -72,13 +69,6 @@ internal fun MainActivity.Navigation() {
|
|||
WrapCheckForUpdate()
|
||||
}
|
||||
}
|
||||
composable(WALLET_ADDRESS_DETAILS) {
|
||||
WrapWalletAddresses(
|
||||
goBack = {
|
||||
navController.popBackStackJustOnce(WALLET_ADDRESS_DETAILS)
|
||||
}
|
||||
)
|
||||
}
|
||||
composable(SETTINGS) {
|
||||
WrapSettings(
|
||||
goAbout = {
|
||||
|
@ -201,5 +191,4 @@ object NavigationTargets {
|
|||
const val SEND = "send"
|
||||
const val SETTINGS = "settings"
|
||||
const val SUPPORT = "support"
|
||||
const val WALLET_ADDRESS_DETAILS = "wallet_address_details"
|
||||
}
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
@file:Suppress("ktlint:standard:filename")
|
||||
|
||||
package co.electriccoin.zcash.ui.screen.address
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import co.electriccoin.zcash.spackle.ClipboardManagerUtil
|
||||
import co.electriccoin.zcash.ui.MainActivity
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
||||
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
|
||||
import co.electriccoin.zcash.ui.screen.address.view.WalletAddresses
|
||||
|
||||
@Composable
|
||||
internal fun MainActivity.WrapWalletAddresses(goBack: () -> Unit) {
|
||||
WrapWalletAddresses(this, goBack)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun WrapWalletAddresses(
|
||||
activity: ComponentActivity,
|
||||
goBack: () -> Unit
|
||||
) {
|
||||
val walletViewModel by activity.viewModels<WalletViewModel>()
|
||||
|
||||
val walletAddresses = walletViewModel.addresses.collectAsStateWithLifecycle().value
|
||||
|
||||
if (null == walletAddresses) {
|
||||
// TODO [#1146]: Consider moving CircularScreenProgressIndicator from Android layer to View layer
|
||||
// TODO [#1146]: Improve this by allowing screen composition and updating it after the data is available
|
||||
// TODO [#1146]: https://github.com/Electric-Coin-Company/zashi-android/issues/1146
|
||||
CircularScreenProgressIndicator()
|
||||
} else {
|
||||
WalletAddresses(
|
||||
walletAddresses,
|
||||
goBack,
|
||||
onCopyToClipboard = { address ->
|
||||
ClipboardManagerUtil.copyToClipboard(
|
||||
activity.applicationContext,
|
||||
activity.getString(R.string.wallet_address_clipboard_tag),
|
||||
address
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
package co.electriccoin.zcash.ui.screen.address
|
||||
|
||||
/**
|
||||
* These are only used for automated testing.
|
||||
*/
|
||||
object WalletAddressesTag {
|
||||
const val WALLET_ADDRESS = "wallet_address_tag"
|
||||
}
|
|
@ -1,295 +0,0 @@
|
|||
@file:Suppress("TooManyFunctions")
|
||||
|
||||
package co.electriccoin.zcash.ui.screen.address.view
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.IntrinsicSize
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
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.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.ArrowDropDownCircle
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.painter.ColorPainter
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cash.z.ecc.android.sdk.fixture.WalletAddressesFixture
|
||||
import cash.z.ecc.android.sdk.model.WalletAddresses
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT
|
||||
import co.electriccoin.zcash.ui.design.component.Body
|
||||
import co.electriccoin.zcash.ui.design.component.GradientSurface
|
||||
import co.electriccoin.zcash.ui.design.component.ListHeader
|
||||
import co.electriccoin.zcash.ui.design.component.ListItem
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.screen.address.WalletAddressesTag
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
@Preview("WalletAddresses")
|
||||
@Composable
|
||||
private fun ComposablePreview() {
|
||||
ZcashTheme(forceDarkMode = false) {
|
||||
GradientSurface {
|
||||
WalletAddresses(
|
||||
runBlocking { WalletAddressesFixture.new() },
|
||||
onBack = {},
|
||||
onCopyToClipboard = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WalletAddresses(
|
||||
walletAddresses: WalletAddresses,
|
||||
onBack: () -> Unit,
|
||||
onCopyToClipboard: (String) -> Unit
|
||||
) {
|
||||
Column {
|
||||
WalletDetailTopAppBar(onBack)
|
||||
WalletDetailAddresses(
|
||||
walletAddresses = walletAddresses,
|
||||
onCopyToClipboard = onCopyToClipboard,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
private fun WalletDetailTopAppBar(onBack: () -> Unit) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.wallet_address_title)
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
onClick = onBack
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowBack,
|
||||
contentDescription = stringResource(R.string.wallet_address_back_content_description)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private val BIG_INDICATOR_WIDTH = 24.dp
|
||||
private val SMALL_INDICATOR_WIDTH = 16.dp
|
||||
|
||||
@Composable
|
||||
private fun WalletDetailAddresses(
|
||||
walletAddresses: WalletAddresses,
|
||||
onCopyToClipboard: (String) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Column(modifier) {
|
||||
Row(
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.height(IntrinsicSize.Min)
|
||||
) {
|
||||
Image(
|
||||
painter = ColorPainter(ZcashTheme.colors.highlight),
|
||||
contentDescription = "",
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.width(BIG_INDICATOR_WIDTH)
|
||||
)
|
||||
|
||||
Column(Modifier.fillMaxWidth()) {
|
||||
ExpandableRow(
|
||||
title = stringResource(R.string.wallet_address_unified),
|
||||
content = walletAddresses.unified.address,
|
||||
isInitiallyExpanded = true,
|
||||
onCopyToClipboard = onCopyToClipboard
|
||||
)
|
||||
|
||||
Box(Modifier.height(IntrinsicSize.Min)) {
|
||||
Divider(modifier = Modifier.fillMaxHeight())
|
||||
ListHeader(
|
||||
text = stringResource(R.string.wallet_address_header_includes),
|
||||
modifier = Modifier.padding(all = ZcashTheme.dimens.spacingSmall)
|
||||
)
|
||||
}
|
||||
|
||||
SaplingAddress(
|
||||
saplingAddress = walletAddresses.sapling.address,
|
||||
onCopyToClipboard = onCopyToClipboard,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.height(IntrinsicSize.Min)
|
||||
)
|
||||
|
||||
TransparentAddress(
|
||||
transparentAddress = walletAddresses.transparent.address,
|
||||
onCopyToClipboard = onCopyToClipboard,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.height(IntrinsicSize.Min)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note: The addresses code below has opportunities to be made more DRY.
|
||||
// Refactoring that is being held off until issue #160 is fixed, since knowledge
|
||||
// of row position will be needed.
|
||||
|
||||
@Composable
|
||||
private fun SaplingAddress(
|
||||
saplingAddress: String,
|
||||
onCopyToClipboard: (String) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(modifier) {
|
||||
SmallIndicator(ZcashTheme.colors.addressHighlightSapling)
|
||||
|
||||
ExpandableRow(
|
||||
title = stringResource(R.string.wallet_address_sapling),
|
||||
content = saplingAddress,
|
||||
isInitiallyExpanded = false,
|
||||
onCopyToClipboard = onCopyToClipboard
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TransparentAddress(
|
||||
transparentAddress: String,
|
||||
onCopyToClipboard: (String) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(modifier) {
|
||||
SmallIndicator(ZcashTheme.colors.addressHighlightTransparent)
|
||||
ExpandableRow(
|
||||
title = stringResource(R.string.wallet_address_transparent),
|
||||
content = transparentAddress,
|
||||
isInitiallyExpanded = false,
|
||||
onCopyToClipboard = onCopyToClipboard
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ExpandableRow(
|
||||
title: String,
|
||||
content: String,
|
||||
isInitiallyExpanded: Boolean,
|
||||
onCopyToClipboard: (String) -> Unit
|
||||
) {
|
||||
var expandedState by rememberSaveable { mutableStateOf(isInitiallyExpanded) }
|
||||
|
||||
Column {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier =
|
||||
Modifier
|
||||
.defaultMinSize(minHeight = 48.dp)
|
||||
.clickable { expandedState = !expandedState }
|
||||
.padding(
|
||||
horizontal = ZcashTheme.dimens.spacingDefault,
|
||||
vertical = ZcashTheme.dimens.spacingTiny
|
||||
)
|
||||
) {
|
||||
ListItem(text = title)
|
||||
Spacer(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(MINIMAL_WEIGHT)
|
||||
)
|
||||
ExpandableArrow(expandedState)
|
||||
}
|
||||
if (expandedState) {
|
||||
Body(
|
||||
content,
|
||||
modifier =
|
||||
Modifier
|
||||
.clickable { onCopyToClipboard(content) }
|
||||
.padding(
|
||||
horizontal = ZcashTheme.dimens.spacingDefault,
|
||||
vertical = ZcashTheme.dimens.spacingTiny
|
||||
)
|
||||
.testTag(WalletAddressesTag.WALLET_ADDRESS)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SmallIndicator(color: Color) {
|
||||
// TODO [#160]: Border is not the right implementation here, as it causes double thickness for the middle item
|
||||
// TODO [#160]: https://github.com/Electric-Coin-Company/zashi-android/issues/160
|
||||
Image(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.width(SMALL_INDICATOR_WIDTH)
|
||||
.border(1.dp, ZcashTheme.colors.addressHighlightBorder),
|
||||
painter = ColorPainter(color),
|
||||
contentDescription = ""
|
||||
)
|
||||
}
|
||||
|
||||
private const val NINETY_DEGREES = 90f
|
||||
|
||||
@Composable
|
||||
private fun ExpandableArrow(isExpanded: Boolean) {
|
||||
Icon(
|
||||
imageVector = Icons.Filled.ArrowDropDownCircle,
|
||||
contentDescription =
|
||||
if (isExpanded) {
|
||||
stringResource(id = R.string.wallet_address_hide)
|
||||
} else {
|
||||
stringResource(id = R.string.wallet_address_show)
|
||||
},
|
||||
modifier =
|
||||
if (isExpanded) {
|
||||
Modifier
|
||||
} else {
|
||||
Modifier.rotate(NINETY_DEGREES)
|
||||
},
|
||||
tint = MaterialTheme.colorScheme.onBackground
|
||||
)
|
||||
}
|
|
@ -17,8 +17,8 @@ import co.electriccoin.zcash.ui.R
|
|||
import co.electriccoin.zcash.ui.common.model.VersionInfo
|
||||
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
||||
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
|
||||
import co.electriccoin.zcash.ui.screen.exportdata.util.FileShareUtil
|
||||
import co.electriccoin.zcash.ui.screen.exportdata.view.ExportPrivateData
|
||||
import co.electriccoin.zcash.ui.util.FileShareUtil
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
|
@ -93,6 +93,7 @@ fun shareData(
|
|||
context = context,
|
||||
network = ZcashNetwork.fromResources(context)
|
||||
),
|
||||
fileType = FileShareUtil.ZASHI_INTERNAL_DATA_MIME_TYPE,
|
||||
versionInfo = VersionInfo.new(context.applicationContext)
|
||||
)
|
||||
runCatching {
|
||||
|
|
|
@ -26,7 +26,6 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
|||
@Composable
|
||||
internal fun MainActivity.WrapHome(
|
||||
onPageChange: (HomeScreenIndex) -> Unit,
|
||||
goAddressDetails: () -> Unit,
|
||||
goBack: () -> Unit,
|
||||
goHistory: () -> Unit,
|
||||
goSettings: () -> Unit,
|
||||
|
@ -36,7 +35,6 @@ internal fun MainActivity.WrapHome(
|
|||
WrapHome(
|
||||
this,
|
||||
onPageChange = onPageChange,
|
||||
goAddressDetails = goAddressDetails,
|
||||
goBack = goBack,
|
||||
goHistory = goHistory,
|
||||
goScan = goScan,
|
||||
|
@ -49,7 +47,6 @@ internal fun MainActivity.WrapHome(
|
|||
@Composable
|
||||
internal fun WrapHome(
|
||||
activity: ComponentActivity,
|
||||
goAddressDetails: () -> Unit,
|
||||
goBack: () -> Unit,
|
||||
goHistory: () -> Unit,
|
||||
goSettings: () -> Unit,
|
||||
|
@ -117,7 +114,6 @@ internal fun WrapHome(
|
|||
WrapReceive(
|
||||
activity = activity,
|
||||
onSettings = goSettings,
|
||||
onAddressDetails = goAddressDetails,
|
||||
)
|
||||
}
|
||||
),
|
||||
|
|
|
@ -2,48 +2,134 @@
|
|||
|
||||
package co.electriccoin.zcash.ui.screen.receive
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.graphics.asAndroidBitmap
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import cash.z.ecc.android.sdk.model.WalletAddresses
|
||||
import co.electriccoin.zcash.spackle.ClipboardManagerUtil
|
||||
import co.electriccoin.zcash.spackle.Twig
|
||||
import co.electriccoin.zcash.spackle.getInternalCacheDirSuspend
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.model.VersionInfo
|
||||
import co.electriccoin.zcash.ui.common.viewmodel.WalletViewModel
|
||||
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
|
||||
import co.electriccoin.zcash.ui.screen.receive.view.Receive
|
||||
import co.electriccoin.zcash.ui.util.FileShareUtil
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
@Composable
|
||||
internal fun WrapReceive(
|
||||
activity: ComponentActivity,
|
||||
onSettings: () -> Unit,
|
||||
onAddressDetails: () -> Unit,
|
||||
) {
|
||||
val viewModel by activity.viewModels<WalletViewModel>()
|
||||
val walletAddresses = viewModel.addresses.collectAsStateWithLifecycle().value
|
||||
|
||||
WrapReceive(
|
||||
walletAddresses,
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val versionInfo = VersionInfo.new(activity.applicationContext)
|
||||
|
||||
Receive(
|
||||
walletAddress = walletAddresses,
|
||||
snackbarHostState = snackbarHostState,
|
||||
onAdjustBrightness = { /* Just for testing purposes */ },
|
||||
onAddrCopyToClipboard = { address ->
|
||||
ClipboardManagerUtil.copyToClipboard(
|
||||
activity.applicationContext,
|
||||
activity.getString(R.string.receive_clipboard_tag),
|
||||
address
|
||||
)
|
||||
},
|
||||
onQrImageShare = { imageBitmap ->
|
||||
scope.launch {
|
||||
shareData(
|
||||
context = activity.applicationContext,
|
||||
snackbarHostState = snackbarHostState,
|
||||
qrImageBitmap = imageBitmap.asAndroidBitmap(),
|
||||
versionInfo = versionInfo
|
||||
).collect { shareResult ->
|
||||
Twig.info {
|
||||
if (shareResult) {
|
||||
"Sharing the address QR code was successful"
|
||||
} else {
|
||||
"Sharing the address QR code failed"
|
||||
}
|
||||
}
|
||||
// No other action for now
|
||||
}
|
||||
}
|
||||
},
|
||||
onSettings = onSettings,
|
||||
onAddressDetails = onAddressDetails,
|
||||
versionInfo = versionInfo
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun WrapReceive(
|
||||
walletAddresses: WalletAddresses?,
|
||||
onSettings: () -> Unit,
|
||||
onAddressDetails: () -> Unit,
|
||||
) {
|
||||
if (null == walletAddresses) {
|
||||
// TODO [#1146]: Consider moving CircularScreenProgressIndicator from Android layer to View layer
|
||||
// TODO [#1146]: Improve this by allowing screen composition and updating it after the data is available
|
||||
// TODO [#1146]: https://github.com/Electric-Coin-Company/zashi-android/issues/1146
|
||||
CircularScreenProgressIndicator()
|
||||
} else {
|
||||
Receive(
|
||||
walletAddresses.unified,
|
||||
onSettings = onSettings,
|
||||
onAddressDetails = onAddressDetails,
|
||||
onAdjustBrightness = { /* Just for testing */ }
|
||||
private const val CACHE_SUBDIR = "zcash_address_qr_images" // NON-NLS
|
||||
private const val TEMP_FILE_NAME_PREFIX = "zcash_address_qr_" // NON-NLS
|
||||
private const val TEMP_FILE_NAME_SUFFIX = ".png" // NON-NLS
|
||||
|
||||
fun shareData(
|
||||
context: Context,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
qrImageBitmap: Bitmap,
|
||||
versionInfo: VersionInfo
|
||||
): Flow<Boolean> =
|
||||
callbackFlow {
|
||||
// Initialize cache directory
|
||||
val cacheDir = context.getInternalCacheDirSuspend(CACHE_SUBDIR)
|
||||
|
||||
// Save the bitmap to a temporary file in the cache directory
|
||||
val bitmapFile =
|
||||
withContext(Dispatchers.IO) {
|
||||
File.createTempFile(
|
||||
TEMP_FILE_NAME_PREFIX,
|
||||
TEMP_FILE_NAME_SUFFIX,
|
||||
cacheDir,
|
||||
).also {
|
||||
it.storeBitmap(qrImageBitmap)
|
||||
}
|
||||
}
|
||||
|
||||
// Example of the expected temporary file path:
|
||||
// /data/user/0/co.electriccoin.zcash.debug/cache/zcash_address_qr_images/
|
||||
// zcash_address_qr_6455164324646067652.png
|
||||
|
||||
val shareIntent =
|
||||
FileShareUtil.newShareContentIntent(
|
||||
context = context,
|
||||
dataFilePath = bitmapFile.absolutePath,
|
||||
versionInfo = versionInfo,
|
||||
fileType = FileShareUtil.ZASHI_QR_CODE_MIME_TYPE
|
||||
)
|
||||
runCatching {
|
||||
context.startActivity(shareIntent)
|
||||
trySend(true)
|
||||
}.onFailure {
|
||||
snackbarHostState.showSnackbar(message = context.getString(R.string.receive_data_unable_to_share))
|
||||
trySend(false)
|
||||
}
|
||||
awaitClose {
|
||||
// No resources to release
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun File.storeBitmap(bitmap: Bitmap) =
|
||||
withContext(Dispatchers.IO) {
|
||||
outputStream().use { fOut ->
|
||||
@Suppress("MagicNumber")
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fOut)
|
||||
fOut.flush()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,10 +8,7 @@ object JvmQrCodeGenerator : QrCodeGenerator {
|
|||
data: String,
|
||||
sizePixels: Int
|
||||
): BooleanArray {
|
||||
val bitMatrix =
|
||||
QRCodeWriter().let {
|
||||
it.encode(data, BarcodeFormat.QR_CODE, sizePixels, sizePixels)
|
||||
}
|
||||
val bitMatrix = QRCodeWriter().encode(data, BarcodeFormat.QR_CODE, sizePixels, sizePixels)
|
||||
|
||||
return BooleanArray(sizePixels * sizePixels).apply {
|
||||
var booleanArrayPosition = 0
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
package co.electriccoin.zcash.ui.screen.receive.view
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
|
@ -13,34 +18,41 @@ import androidx.compose.material.icons.filled.BrightnessHigh
|
|||
import androidx.compose.material.icons.filled.BrightnessLow
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cash.z.ecc.android.sdk.fixture.WalletAddressFixture
|
||||
import cash.z.ecc.android.sdk.fixture.WalletAddressesFixture
|
||||
import cash.z.ecc.android.sdk.model.WalletAddress
|
||||
import cash.z.ecc.android.sdk.model.WalletAddresses
|
||||
import co.electriccoin.zcash.ui.R
|
||||
import co.electriccoin.zcash.ui.common.BrightenScreen
|
||||
import co.electriccoin.zcash.ui.common.DisableScreenTimeout
|
||||
import co.electriccoin.zcash.ui.common.model.VersionInfo
|
||||
import co.electriccoin.zcash.ui.common.test.CommonTag
|
||||
import co.electriccoin.zcash.ui.design.MINIMAL_WEIGHT
|
||||
import co.electriccoin.zcash.ui.design.component.Body
|
||||
import co.electriccoin.zcash.ui.design.component.CircularScreenProgressIndicator
|
||||
import co.electriccoin.zcash.ui.design.component.GradientSurface
|
||||
import co.electriccoin.zcash.ui.design.component.PrimaryButton
|
||||
import co.electriccoin.zcash.ui.design.component.Reference
|
||||
import co.electriccoin.zcash.ui.design.component.SmallTopAppBar
|
||||
import co.electriccoin.zcash.ui.design.component.SubHeader
|
||||
import co.electriccoin.zcash.ui.design.theme.ZcashTheme
|
||||
import co.electriccoin.zcash.ui.fixture.VersionInfoFixture
|
||||
import co.electriccoin.zcash.ui.screen.receive.util.AndroidQrCodeImageGenerator
|
||||
import co.electriccoin.zcash.ui.screen.receive.util.JvmQrCodeGenerator
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
@ -52,58 +64,77 @@ private fun ComposablePreview() {
|
|||
ZcashTheme(forceDarkMode = false) {
|
||||
GradientSurface {
|
||||
Receive(
|
||||
walletAddress = runBlocking { WalletAddressFixture.unified() },
|
||||
walletAddress = runBlocking { WalletAddressesFixture.new() },
|
||||
snackbarHostState = SnackbarHostState(),
|
||||
onSettings = {},
|
||||
onAddressDetails = {},
|
||||
onAdjustBrightness = {},
|
||||
onAddrCopyToClipboard = {},
|
||||
onQrImageShare = {},
|
||||
versionInfo = VersionInfoFixture.new()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
fun Receive(
|
||||
walletAddress: WalletAddress,
|
||||
walletAddress: WalletAddresses?,
|
||||
snackbarHostState: SnackbarHostState,
|
||||
onSettings: () -> Unit,
|
||||
onAddressDetails: () -> Unit,
|
||||
onAdjustBrightness: (Boolean) -> Unit,
|
||||
onAddrCopyToClipboard: (String) -> Unit,
|
||||
onQrImageShare: (ImageBitmap) -> Unit,
|
||||
versionInfo: VersionInfo,
|
||||
) {
|
||||
val (brightness, setBrightness) = rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
Scaffold(topBar = {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
ReceiveTopAppBar(
|
||||
adjustBrightness = brightness,
|
||||
onSettings = onSettings,
|
||||
onBrightness = {
|
||||
onAdjustBrightness(!brightness)
|
||||
setBrightness(!brightness)
|
||||
}
|
||||
},
|
||||
versionInfo = versionInfo,
|
||||
)
|
||||
}) { paddingValues ->
|
||||
},
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||
) { paddingValues ->
|
||||
if (null == walletAddress) {
|
||||
CircularScreenProgressIndicator()
|
||||
} else {
|
||||
ReceiveContents(
|
||||
walletAddress = walletAddress,
|
||||
onAddressDetails = onAddressDetails,
|
||||
onAddressCopyToClipboard = onAddrCopyToClipboard,
|
||||
onQrImageShare = onQrImageShare,
|
||||
adjustBrightness = brightness,
|
||||
versionInfo = versionInfo,
|
||||
modifier =
|
||||
Modifier.padding(
|
||||
top = paddingValues.calculateTopPadding() + ZcashTheme.dimens.spacingDefault,
|
||||
bottom = paddingValues.calculateBottomPadding() + ZcashTheme.dimens.spacingHuge,
|
||||
bottom = paddingValues.calculateBottomPadding() + ZcashTheme.dimens.spacingDefault,
|
||||
start = ZcashTheme.dimens.screenHorizontalSpacingRegular,
|
||||
end = ZcashTheme.dimens.screenHorizontalSpacingRegular
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ReceiveTopAppBar(
|
||||
adjustBrightness: Boolean,
|
||||
onSettings: () -> Unit,
|
||||
onBrightness: () -> Unit
|
||||
onBrightness: () -> Unit,
|
||||
versionInfo: VersionInfo
|
||||
) {
|
||||
SmallTopAppBar(
|
||||
titleText = stringResource(id = R.string.receive_title),
|
||||
regularActions = {
|
||||
if (versionInfo.isDebuggable) {
|
||||
IconButton(
|
||||
onClick = onBrightness
|
||||
) {
|
||||
|
@ -117,6 +148,7 @@ private fun ReceiveTopAppBar(
|
|||
contentDescription = stringResource(R.string.receive_brightness_content_description)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
hamburgerMenuActions = {
|
||||
IconButton(
|
||||
|
@ -132,13 +164,14 @@ private fun ReceiveTopAppBar(
|
|||
)
|
||||
}
|
||||
|
||||
private val DEFAULT_QR_CODE_SIZE = 320.dp
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
private fun ReceiveContents(
|
||||
walletAddress: WalletAddress,
|
||||
onAddressDetails: () -> Unit,
|
||||
walletAddress: WalletAddresses,
|
||||
onAddressCopyToClipboard: (String) -> Unit,
|
||||
onQrImageShare: (ImageBitmap) -> Unit,
|
||||
adjustBrightness: Boolean,
|
||||
versionInfo: VersionInfo,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
|
@ -149,76 +182,160 @@ private fun ReceiveContents(
|
|||
.then(modifier),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
QrCode(
|
||||
data = walletAddress.address,
|
||||
size = DEFAULT_QR_CODE_SIZE,
|
||||
adjustBrightness = adjustBrightness,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingLarge))
|
||||
|
||||
Body(
|
||||
text = stringResource(id = R.string.wallet_address_unified),
|
||||
Modifier.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
||||
|
||||
// TODO [#163]: Ellipsize center of the string
|
||||
// TODO [#163]: https://github.com/Electric-Coin-Company/zashi-android/issues/163
|
||||
Text(
|
||||
text = walletAddress.address,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = MaterialTheme.colorScheme.onBackground,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1
|
||||
)
|
||||
|
||||
Spacer(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(MINIMAL_WEIGHT)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingDefault))
|
||||
|
||||
PrimaryButton(
|
||||
onClick = onAddressDetails,
|
||||
text = stringResource(id = R.string.receive_see_address_details)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun QrCode(
|
||||
data: String,
|
||||
size: Dp,
|
||||
modifier: Modifier = Modifier,
|
||||
adjustBrightness: Boolean = false,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
if (adjustBrightness) {
|
||||
BrightenScreen()
|
||||
DisableScreenTimeout()
|
||||
}
|
||||
|
||||
val sizePixels = with(LocalDensity.current) { size.toPx() }.roundToInt()
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
||||
|
||||
Address(
|
||||
walletAddress = walletAddress.unified,
|
||||
onAddressCopyToClipboard = onAddressCopyToClipboard,
|
||||
onQrImageShare = onQrImageShare,
|
||||
)
|
||||
|
||||
if (versionInfo.isTestnet) {
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingHuge))
|
||||
|
||||
Address(
|
||||
walletAddress = walletAddress.sapling,
|
||||
onAddressCopyToClipboard = onAddressCopyToClipboard,
|
||||
onQrImageShare = onQrImageShare,
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingHuge))
|
||||
|
||||
Address(
|
||||
walletAddress = walletAddress.transparent,
|
||||
onAddressCopyToClipboard = onAddressCopyToClipboard,
|
||||
onQrImageShare = onQrImageShare,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val DEFAULT_QR_CODE_SIZE = 320.dp
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun Address(
|
||||
walletAddress: WalletAddress,
|
||||
onAddressCopyToClipboard: (String) -> Unit,
|
||||
onQrImageShare: (ImageBitmap) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(modifier = modifier) {
|
||||
SubHeader(
|
||||
text =
|
||||
stringResource(
|
||||
id =
|
||||
when (walletAddress) {
|
||||
is WalletAddress.Unified -> R.string.receive_wallet_address_unified
|
||||
is WalletAddress.Sapling -> R.string.receive_wallet_address_sapling
|
||||
is WalletAddress.Transparent -> R.string.receive_wallet_address_transparent
|
||||
}
|
||||
),
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingTiny))
|
||||
|
||||
val sizePixels = with(LocalDensity.current) { DEFAULT_QR_CODE_SIZE.toPx() }.roundToInt()
|
||||
val qrCodeImage =
|
||||
remember {
|
||||
qrCodeForAddress(
|
||||
address = walletAddress.address,
|
||||
size = sizePixels
|
||||
)
|
||||
}
|
||||
|
||||
QrCode(
|
||||
qrCodeImage = qrCodeImage,
|
||||
onQrImageBitmapShare = onQrImageShare,
|
||||
contentDescription =
|
||||
stringResource(
|
||||
id =
|
||||
when (walletAddress) {
|
||||
is WalletAddress.Unified -> R.string.receive_unified_content_description
|
||||
is WalletAddress.Sapling -> R.string.receive_sapling_content_description
|
||||
is WalletAddress.Transparent -> R.string.receive_transparent_content_description
|
||||
}
|
||||
),
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingTiny))
|
||||
|
||||
// TODO [#163]: Ellipsize center of the string
|
||||
// TODO [#163]: https://github.com/Electric-Coin-Company/zashi-android/issues/163
|
||||
Text(
|
||||
text = walletAddress.address,
|
||||
style = ZcashTheme.typography.primary.bodyLarge,
|
||||
color = ZcashTheme.colors.textDescription,
|
||||
textAlign = TextAlign.Center,
|
||||
modifier =
|
||||
Modifier
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.clickable { onAddressCopyToClipboard(walletAddress.address) }
|
||||
.padding(horizontal = ZcashTheme.dimens.spacingLarge)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(ZcashTheme.dimens.spacingSmall))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Reference(
|
||||
text = stringResource(id = R.string.receive_copy),
|
||||
onClick = { onAddressCopyToClipboard(walletAddress.address) },
|
||||
textAlign = TextAlign.Center,
|
||||
imageVector = ImageVector.vectorResource(R.drawable.copy),
|
||||
imageContentDescription = null,
|
||||
modifier = Modifier.wrapContentSize(),
|
||||
)
|
||||
Reference(
|
||||
text = stringResource(id = R.string.receive_share),
|
||||
onClick = { onQrImageShare(qrCodeImage) },
|
||||
textAlign = TextAlign.Center,
|
||||
imageVector = ImageVector.vectorResource(R.drawable.share),
|
||||
imageContentDescription = null,
|
||||
modifier = Modifier.wrapContentSize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun qrCodeForAddress(
|
||||
address: String,
|
||||
size: Int,
|
||||
): ImageBitmap {
|
||||
// In the future, use actual/expect to switch QR code generator implementations for multiplatform
|
||||
|
||||
// Note that our implementation has an extra array copy to BooleanArray, which is a cross-platform
|
||||
// representation. This should have minimal performance impact since the QR code is relatively
|
||||
// small and we only generate QR codes infrequently.
|
||||
|
||||
val qrCodePixelArray = JvmQrCodeGenerator.generate(data, sizePixels)
|
||||
val qrCodeImage = AndroidQrCodeImageGenerator.generate(qrCodePixelArray, sizePixels)
|
||||
val qrCodePixelArray = JvmQrCodeGenerator.generate(address, size)
|
||||
|
||||
return AndroidQrCodeImageGenerator.generate(qrCodePixelArray, size)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun QrCode(
|
||||
contentDescription: String,
|
||||
qrCodeImage: ImageBitmap,
|
||||
onQrImageBitmapShare: (ImageBitmap) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Image(
|
||||
bitmap = qrCodeImage,
|
||||
contentDescription = stringResource(R.string.receive_qr_code_content_description)
|
||||
contentDescription = contentDescription,
|
||||
modifier =
|
||||
Modifier
|
||||
.clickable { onQrImageBitmapShare(qrCodeImage) }
|
||||
.then(modifier)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,12 +37,13 @@ private const val MAX_EXCEPTIONS_TO_REPORT = 5
|
|||
|
||||
suspend fun CrashInfo.Companion.all(context: Context): List<CrashInfo> {
|
||||
val exceptionDirectory = ExceptionPath.getExceptionDirectory(context) ?: return emptyList()
|
||||
val filesList: List<File> = exceptionDirectory.listFilesSuspend().toList()
|
||||
return filesList
|
||||
.mapNotNull {
|
||||
val filesList: List<File>? = exceptionDirectory.listFilesSuspend()?.toList()
|
||||
return filesList?.run {
|
||||
mapNotNull {
|
||||
ReportedException.new(it)
|
||||
}.sortedBy { it.time }
|
||||
.reversed()
|
||||
.take(MAX_EXCEPTIONS_TO_REPORT)
|
||||
.map { CrashInfo(it.exceptionClassName, it.isUncaught, it.time) }
|
||||
} ?: emptyList()
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package co.electriccoin.zcash.ui.screen.exportdata.util
|
||||
package co.electriccoin.zcash.ui.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
|
@ -13,6 +13,7 @@ object FileShareUtil {
|
|||
const val SHARE_CONTENT_PERMISSION_FLAGS = Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
|
||||
const val ZASHI_INTERNAL_DATA_MIME_TYPE = "application/octet-stream" // NON-NLS
|
||||
const val ZASHI_QR_CODE_MIME_TYPE = "image/png" // NON-NLS
|
||||
|
||||
const val ZASHI_INTERNAL_DATA_AUTHORITY = "co.electriccoin.zcash.provider" // NON-NLS
|
||||
const val ZASHI_INTERNAL_DATA_AUTHORITY_DEBUG = "co.electriccoin.zcash.debug.provider" // NON-NLS
|
||||
|
@ -30,7 +31,8 @@ object FileShareUtil {
|
|||
internal fun newShareContentIntent(
|
||||
context: Context,
|
||||
dataFilePath: String,
|
||||
versionInfo: VersionInfo
|
||||
fileType: String,
|
||||
versionInfo: VersionInfo,
|
||||
): Intent {
|
||||
val fileUri =
|
||||
FileProvider.getUriForFile(
|
||||
|
@ -43,7 +45,7 @@ object FileShareUtil {
|
|||
Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_STREAM, fileUri)
|
||||
type = ZASHI_INTERNAL_DATA_MIME_TYPE
|
||||
type = fileType
|
||||
}
|
||||
|
||||
val shareDataIntent =
|
|
@ -1,21 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<!-- TODO [#1183]: Rework the way we grant access to file provider -->
|
||||
<!-- TODO [#1183]: https://github.com/Electric-Coin-Company/zashi-android/issues/1183 -->
|
||||
|
||||
<!-- Android Studio complains about root-path. Search for an alternative way of approaching no_backup folder -->
|
||||
<!-- Another way is to split paths into packages depending on current build type -->
|
||||
|
||||
<root-path
|
||||
name="root_mainnet_release"
|
||||
path="/data/data/co.electriccoin.zcash/no_backup/co.electricoin.zcash/."
|
||||
path="/data/data/co.electriccoin.zcash/."
|
||||
/>
|
||||
<root-path
|
||||
name="root_mainnet_debug"
|
||||
path="/data/data/co.electriccoin.zcash.debug/no_backup/co.electricoin.zcash/."
|
||||
path="/data/data/co.electriccoin.zcash.debug/."
|
||||
/>
|
||||
<root-path
|
||||
name="root_testnet_release"
|
||||
path="/data/data/co.electriccoin.zcash.testnet/no_backup/co.electricoin.zcash/."
|
||||
path="/data/data/co.electriccoin.zcash.testnet/."
|
||||
/>
|
||||
<root-path
|
||||
name="root_testnet_debug"
|
||||
path="/data/data/co.electriccoin.zcash.testnet.debug/no_backup/co.electricoin.zcash/."
|
||||
path="/data/data/co.electriccoin.zcash.testnet.debug/."
|
||||
/>
|
||||
</paths>
|
|
@ -0,0 +1,16 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="16">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h16v16h-16z"/>
|
||||
<path
|
||||
android:pathData="M0,16V2.823H13.206V16H0ZM11.832,14.605V4.218H1.374V14.622H11.832V14.605Z"
|
||||
android:fillColor="#000000"/>
|
||||
<path
|
||||
android:pathData="M15.313,11.816C14.939,11.816 14.627,11.504 14.627,11.126V1.395H4.839C4.465,1.395 4.152,1.083 4.152,0.706C4.152,0.328 4.465,0.017 4.839,0.017H16V11.143C16,11.52 15.688,11.832 15.313,11.832V11.816Z"
|
||||
android:fillColor="#000000"/>
|
||||
</group>
|
||||
</vector>
|
|
@ -0,0 +1,40 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="14dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="14">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0.155h16v13.58h-16z"/>
|
||||
<path
|
||||
android:pathData="M16,12.005H0V13.735H16V12.005Z"
|
||||
android:fillColor="#231F20"/>
|
||||
<path
|
||||
android:pathData="M15.5,12.505H0.5V13.235H15.5V12.505Z"
|
||||
android:fillColor="#231F20"/>
|
||||
<path
|
||||
android:pathData="M1.7,8.465H0V13.735H1.7V8.465Z"
|
||||
android:fillColor="#231F20"/>
|
||||
<path
|
||||
android:pathData="M1.2,8.965H0.5V13.235H1.2V8.965Z"
|
||||
android:fillColor="#231F20"/>
|
||||
<path
|
||||
android:pathData="M16,8.465H14.3V13.735H16V8.465Z"
|
||||
android:fillColor="#231F20"/>
|
||||
<path
|
||||
android:pathData="M15.5,8.965H14.8V13.235H15.5V8.965Z"
|
||||
android:fillColor="#231F20"/>
|
||||
<path
|
||||
android:pathData="M8.85,1.175H7.15V8.335H8.85V1.175Z"
|
||||
android:fillColor="#231F20"/>
|
||||
<path
|
||||
android:pathData="M8.35,1.675H7.65V7.835H8.35V1.675Z"
|
||||
android:fillColor="#231F20"/>
|
||||
<path
|
||||
android:pathData="M8.5,1.395L8,0.875L7.5,1.395L5.42,3.555L5.92,4.075L7.99,1.915L10.07,4.075L10.57,3.555L8.5,1.395Z"
|
||||
android:fillColor="#231F20"/>
|
||||
<path
|
||||
android:pathData="M8,2.625L5.93,4.785L4.73,3.555L8,0.155L11.26,3.555L10.08,4.785L8,2.625Z"
|
||||
android:fillColor="#231F20"/>
|
||||
</group>
|
||||
</vector>
|
|
@ -2,8 +2,14 @@
|
|||
<resources>
|
||||
<string name="receive_title">Receive</string>
|
||||
<string name="receive_brightness_content_description">Adjust brightness</string>
|
||||
<string name="receive_qr_code_content_description">QR code for address</string>
|
||||
<string name="receive_caption">Your Address</string>
|
||||
<string name="receive_see_address_details">See Address Details</string>
|
||||
|
||||
<string name="receive_unified_content_description">Unified Address QR code</string>
|
||||
<string name="receive_sapling_content_description">Sapling Address QR code</string>
|
||||
<string name="receive_transparent_content_description">Transparent Address QR code</string>
|
||||
<string name="receive_wallet_address_unified">Unified Address</string>
|
||||
<string name="receive_wallet_address_sapling">Sapling Address</string>
|
||||
<string name="receive_wallet_address_transparent">Transparent Address</string>
|
||||
<string name="receive_copy">Copy</string>
|
||||
<string name="receive_share">Share</string>
|
||||
<string name="receive_clipboard_tag">Zcash Wallet Address</string>
|
||||
<string name="receive_data_unable_to_share">Unable to find an application to share the QR code with.</string>
|
||||
</resources>
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="wallet_address_title">My wallet addresses</string>
|
||||
<string name="wallet_address_back_content_description">Back</string>
|
||||
|
||||
<string name="wallet_address_unified">Your Unified Address:</string>
|
||||
<string name="wallet_address_header_includes">which includes</string>
|
||||
<string name="wallet_address_sapling">Shielded Sapling (NU1)</string>
|
||||
<string name="wallet_address_transparent">Transparent</string>
|
||||
<string name="wallet_address_show">Show address</string>
|
||||
<string name="wallet_address_hide">Hide address</string>
|
||||
|
||||
<string name="wallet_address_clipboard_tag">Zcash Wallet Address</string>
|
||||
</resources>
|
|
@ -3,9 +3,9 @@
|
|||
<string name="not_enough_space_title">Not enough space!</string>
|
||||
<string name="not_enough_space_logo_content_description"></string>
|
||||
<string name="not_enough_space_description">You need approximately <xliff:g example="1" id="required_gigabytes">
|
||||
%1$d</xliff:g> gig of space while synchronizing the Zcash blockchain, but only 300 megs once done. Syncing
|
||||
%1$d</xliff:g> Gbyte of space while synchronizing the Zcash blockchain, but only 300 Mbyte once done. Syncing
|
||||
will stay paused until more space is available.</string>
|
||||
<string name="space_required_to_continue"><xliff:g example="300" id="required_megabytes">~%1$d</xliff:g> megs
|
||||
<string name="space_required_to_continue"><xliff:g example="300" id="required_megabytes">~%1$d</xliff:g> Mbyte
|
||||
required to continue </string>
|
||||
<string name="unknown">Unknown</string>
|
||||
</resources>
|
||||
|
|
|
@ -290,9 +290,6 @@ class ScreenshotTest : UiTestPrerequisites() {
|
|||
composeTestRule.navigateInHomeTab(HomeTag.TAB_BALANCES)
|
||||
balancesScreenshots(resContext, tag, composeTestRule)
|
||||
|
||||
navigateTo(NavigationTargets.WALLET_ADDRESS_DETAILS)
|
||||
addressDetailsScreenshots(resContext, tag, composeTestRule)
|
||||
|
||||
navigateTo(NavigationTargets.HISTORY)
|
||||
transactionHistoryScreenshots(resContext, tag, composeTestRule)
|
||||
|
||||
|
@ -419,18 +416,6 @@ private fun settingsScreenshots(
|
|||
ScreenshotTest.takeScreenshot(tag, "Settings 1")
|
||||
}
|
||||
|
||||
private fun addressDetailsScreenshots(
|
||||
resContext: Context,
|
||||
tag: String,
|
||||
composeTestRule: ComposeTestRule
|
||||
) {
|
||||
composeTestRule.onNode(hasText(resContext.getString(R.string.wallet_address_title))).also {
|
||||
it.assertExists()
|
||||
}
|
||||
|
||||
ScreenshotTest.takeScreenshot(tag, "Addresses 1")
|
||||
}
|
||||
|
||||
private fun transactionHistoryScreenshots(
|
||||
resContext: Context,
|
||||
tag: String,
|
||||
|
@ -468,7 +453,7 @@ private fun receiveZecScreenshots(
|
|||
|
||||
composeTestRule.onNode(
|
||||
hasContentDescription(
|
||||
value = resContext.getString(R.string.receive_qr_code_content_description),
|
||||
value = resContext.getString(R.string.receive_unified_content_description),
|
||||
ignoreCase = true
|
||||
)
|
||||
).also {
|
||||
|
|
Loading…
Reference in New Issue