Merge pull request #207 from zcash/feature/localization-release

Feature/localization release
This commit is contained in:
Kevin Gorham 2020-10-09 13:28:08 -04:00 committed by GitHub
commit 672dfc7d47
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
78 changed files with 1875 additions and 865 deletions

View File

@ -1,6 +1,16 @@
Change Log Change Log
========== ==========
Version 1.0.0-alpha37 *(2020-10-07)*
------------------------------------
- New: Localization in 5 languages Russian, Italian, Spanish, Chinese and Korean.
- New: Store and sync using just the ViewingKey.
- New: Added QA build flavor for better testing.
- New: Ability to change servers (thanks @Nighthawk!)
- Fix: Critical bug in 3rd-party secure storage library impacting large strings.
- Fix: Devices without PIN can use the wallet again.
- Fix: Developer logs now work on all devices.
Version 1.0.0-alpha34 *(2020-08-28)* Version 1.0.0-alpha34 *(2020-08-28)*
------------------------------------ ------------------------------------
- New: Implemented transaction detail view. - New: Implemented transaction detail view.

View File

@ -9,7 +9,7 @@ apply plugin: 'com.google.gms.google-services'
archivesBaseName = 'zcash-android-wallet' archivesBaseName = 'zcash-android-wallet'
group = 'cash.z.ecc.android' group = 'cash.z.ecc.android'
version = '1.0.0-alpha34' version = Deps.versionName
android { android {
ndkVersion "21.1.6352462" ndkVersion "21.1.6352462"
@ -17,16 +17,16 @@ android {
buildToolsVersion Deps.buildToolsVersion buildToolsVersion Deps.buildToolsVersion
viewBinding.enabled = true viewBinding.enabled = true
defaultConfig { defaultConfig {
applicationId 'cash.z.ecc.android' applicationId Deps.packageName
minSdkVersion Deps.minSdkVersion minSdkVersion Deps.minSdkVersion
targetSdkVersion Deps.targetSdkVersion targetSdkVersion Deps.targetSdkVersion
versionCode = 1_00_00_034 versionCode = Deps.versionCode
// last digits are alpha(0XX) beta(2XX) rc(4XX) release(8XX) dev(9XX). Ex: 1_08_04_401 is an release candidate build of version 1.8.4 and 1_08_04_800 would be the final release. versionName = Deps.versionName
versionName = "$version"
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
testInstrumentationRunnerArguments clearPackageData: 'true' testInstrumentationRunnerArguments clearPackageData: 'true'
multiDexEnabled true multiDexEnabled true
resValue 'string', 'bugsnag_api_key', "${(project.findProperty('BUGSNAG_API_KEY') ?: System.getenv('BUGSNAG_API_KEY')) ?: ''}" resValue 'string', 'bugsnag_api_key', "${(project.findProperty('BUGSNAG_API_KEY') ?: System.getenv('BUGSNAG_API_KEY')) ?: ''}"
resValue 'string', 'file_authority', "${Deps.packageName}.fileprovider"
// this setting allows using color resources in vector drawables, rather than hardcoded values (note: only works when minApi is 21) // this setting allows using color resources in vector drawables, rather than hardcoded values (note: only works when minApi is 21)
// per https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.VectorDrawablesOptions.html: If set to an empty collection, all special handling of vector drawables will be disabled. // per https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.VectorDrawablesOptions.html: If set to an empty collection, all special handling of vector drawables will be disabled.
@ -68,10 +68,15 @@ android {
useProguard false useProguard false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
// mock { // builds for testing only in the wallet team, typically unfinished features
// initWith debug // this flavor can be installed alongside the others
// matchingFallbacks = ['debug', 'release', 'zcashtestnet'] qa {
// } initWith debug
debuggable true
applicationIdSuffix ".internal"
resValue 'string', 'file_authority', "${Deps.packageName}.internal.fileprovider"
matchingFallbacks = ['debug']
}
} }
compileOptions { compileOptions {
@ -80,6 +85,9 @@ android {
} }
kotlinOptions { kotlinOptions {
jvmTarget = "1.8" jvmTarget = "1.8"
freeCompilerArgs += "-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi"
// freeCompilerArgs += "-Xopt-in=kotlinx.coroutines.ObsoleteCoroutinesApi"
// freeCompilerArgs += "-Xopt-in=kotlinx.coroutines.FlowPreview"
} }
kapt { kapt {
arguments { arguments {
@ -94,7 +102,10 @@ android {
} }
applicationVariants.all { variant -> applicationVariants.all { variant ->
variant.outputs.all { variant.outputs.all {
outputFileName = "$archivesBaseName-v${defaultConfig.versionName}-${variant.buildType.name}.apk" if (variant.buildType.name == "qa") {
it.versionNameOverride = "${Deps.versionName}-QA"
}
outputFileName = "$archivesBaseName-v${Deps.versionName}-${variant.buildType.name}.apk"
} }
} }
} }
@ -165,8 +176,12 @@ dependencies {
testImplementation Deps.Test.COROUTINES_TEST testImplementation Deps.Test.COROUTINES_TEST
testImplementation Deps.Test.JUNIT testImplementation Deps.Test.JUNIT
testImplementation Deps.Test.MOKITO testImplementation Deps.Test.MOKITO
testImplementation Deps.Test.MOKITO_KOTLIN
testImplementation Deps.Test.COROUTINES_TEST
testImplementation Deps.Zcash.Sdk.TESTNET
androidTestImplementation Deps.Test.Android.JUNIT androidTestImplementation Deps.Test.Android.JUNIT
androidTestImplementation Deps.Test.Android.ESPRESSO androidTestImplementation Deps.Test.Android.ESPRESSO
} }
defaultTasks 'clean', 'installZcashmainnetRelease' defaultTasks 'clean', 'assembleZcashmainnetRelease'

View File

@ -63,6 +63,35 @@
] ]
} }
} }
},
{
"client_info": {
"mobilesdk_app_id": "1:000000000000:android:8888888888888888888888",
"android_client_info": {
"package_name": "cash.z.ecc.android.internal"
}
},
"oauth_client": [
{
"client_id": "dummy.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "dummy"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "dummy.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
} }
], ],
"configuration_version": "1" "configuration_version": "1"

View File

@ -0,0 +1,149 @@
package cash.z.ecc.android.integration
import cash.z.ecc.android.ext.WalletZecFormmatter
import org.junit.Assert
import org.junit.Before
import org.junit.Test
class ConversionsTest {
// val formatter: WalletZecFormmatter = WalletZecFormmatter()
@Before
fun setUp() {
}
@Test
fun testToZatoshi() {
val input = "1"
val result = WalletZecFormmatter.toZatoshi(input)
Assert.assertEquals(100_000_000L, result)
}
@Test
fun testToZecString_short() {
val input = 112_340_000L
val result = WalletZecFormmatter.toZecStringShort(input)
Assert.assertEquals("1.123", result)
}
@Test
fun testToZecString_shortRoundUp() {
val input = 112_355_600L
val result = WalletZecFormmatter.toZecStringShort(input)
Assert.assertEquals("1.124", result)
}
@Test
fun testToZecString_shortRoundDown() {
val input = 112_349_999L
val result = WalletZecFormmatter.toZecStringShort(input)
Assert.assertEquals("1.123", result)
}
@Test
fun testToZecString_shortRoundHalfEven() {
val input = 112_250_000L
val result = WalletZecFormmatter.toZecStringShort(input)
Assert.assertEquals("1.122", result)
}
@Test
fun testToZecString_shortRoundHalfOdd() {
val input = 112_350_000L
val result = WalletZecFormmatter.toZecStringShort(input)
Assert.assertEquals("1.124", result)
}
@Test
fun testToBigDecimal_noCommas() {
val input = "1000"
val result = WalletZecFormmatter.toBigDecimal(input)!!
Assert.assertEquals(1000, result.longValueExact())
}
@Test
fun testToBigDecimal_thousandComma() {
val input = "1,000"
val result = WalletZecFormmatter.toBigDecimal(input)!!
Assert.assertEquals(1000, result.longValueExact())
}
@Test
fun testToBigDecimal_thousandCommaWithDecimal() {
val input = "1,000.00"
val result = WalletZecFormmatter.toBigDecimal(input)!!
Assert.assertEquals(1000, result.longValueExact())
}
@Test
fun testToBigDecimal_oneDecimal() {
val input = "1.000"
val result = WalletZecFormmatter.toBigDecimal(input)!!
Assert.assertEquals(1, result.longValueExact())
}
@Test
fun testToBigDecimal_thousandWithThinSpace() {
val input = "1000"
val result = WalletZecFormmatter.toBigDecimal(input)!!
Assert.assertEquals(1000, result.longValueExact())
}
@Test
fun testToBigDecimal_oneWithThinSpace() {
val input = "1.000000"
val result = WalletZecFormmatter.toBigDecimal(input)!!
Assert.assertEquals(1, result.longValueExact())
}
@Test
fun testToBigDecimal_oneDecimalWithComma() {
val input = "1.000,00"
val result = WalletZecFormmatter.toBigDecimal(input)!!
Assert.assertEquals(1, result.longValueExact())
}
@Test
fun testToZecString_full() {
val input = 112_341_123L
val result = WalletZecFormmatter.toZecStringFull(input)
Assert.assertEquals("1.12341123", result)
}
@Test
fun testToZecString_fullRoundUp() {
val input = 112_355_678L
val result = WalletZecFormmatter.toZecStringFull(input)
Assert.assertEquals("1.12355678", result)
}
@Test
fun testToZecString_fullRoundDown() {
val input = 112_349_999L
val result = WalletZecFormmatter.toZecStringFull(input)
Assert.assertEquals("1.12349999", result)
}
@Test
fun testToZecString_fullRoundHalfEven() {
val input = 112_250_009L
val result = WalletZecFormmatter.toZecStringFull(input)
Assert.assertEquals("1.12250009", result)
}
@Test
fun testToZecString_fullRoundHalfOdd() {
val input = 112_350_004L
val result = WalletZecFormmatter.toZecStringFull(input)
Assert.assertEquals("1.12350004", result)
}
}

View File

@ -81,8 +81,8 @@ class IntegrationTest {
@Test @Test
fun testAddress() { fun testAddress() {
val seed = mnemonics.toSeed(phrase.toCharArray()) val seed = mnemonics.toSeed(phrase.toCharArray())
val initializer = Initializer(appContext).apply { val initializer = Initializer(appContext) { config ->
new(seed, Initializer.DefaultBirthdayStore(appContext).newWalletBirthday, overwrite = true) config.new(seed)
} }
assertEquals( assertEquals(
"Generated incorrect z-address!", "Generated incorrect z-address!",

View File

@ -0,0 +1,65 @@
package cash.z.ecc.android.integration
import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry
import cash.z.ecc.android.lockbox.LockBox
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
class LockBoxTest {
lateinit var lockBox: LockBox
lateinit var appContext: Context
private val hex = ('a'..'f') + ('0'..'9')
private val iterations = 50
@Before
fun setUp() {
appContext = InstrumentationRegistry.getInstrumentation().targetContext
lockBox = LockBox(appContext)
lockBox.clear()
}
@Test
fun testLongString() {
var successCount = 0
repeat(iterations) {
val sampleHex = List(500) { hex.random() }.joinToString("")
lockBox["longStr"] = sampleHex
val actual: String = lockBox["longStr"]!!
if(sampleHex == actual) successCount++
lockBox.clear()
}
assertEquals(iterations, successCount)
}
@Test
fun testShortString() {
var successCount = 0
repeat(iterations) {
val sampleHex = List(50) { hex.random() }.joinToString("")
lockBox["shortStr"] = sampleHex
val actual: String = lockBox["shortStr"]!!
if(sampleHex == actual) successCount++
lockBox.clear()
}
assertEquals(iterations, successCount)
}
@Test
fun testGiantString() {
var successCount = 0
repeat(iterations) {
val sampleHex = List(2500) { hex.random() }.joinToString("")
lockBox["giantStr"] = sampleHex
val actual: String = lockBox["giantStr"]!!
if(sampleHex == actual) successCount++
lockBox.clear()
}
assertEquals(iterations, successCount)
}
}

View File

@ -22,7 +22,7 @@
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="cash.z.ecc.android.fileprovider" android:authorities="@string/file_authority"
android:exported="false" android:exported="false"
android:grantUriPermissions="true" android:grantUriPermissions="true"
android:writePermission="true"> android:writePermission="true">

View File

@ -10,6 +10,7 @@ import cash.z.ecc.android.lockbox.LockBox
import cash.z.ecc.android.sdk.ext.SilentTwig import cash.z.ecc.android.sdk.ext.SilentTwig
import cash.z.ecc.android.sdk.ext.TroubleshootingTwig import cash.z.ecc.android.sdk.ext.TroubleshootingTwig
import cash.z.ecc.android.sdk.ext.Twig import cash.z.ecc.android.sdk.ext.Twig
import cash.z.ecc.android.ui.util.DebugFileTwig
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.multibindings.IntoSet import dagger.multibindings.IntoSet
@ -53,7 +54,7 @@ class AppModule {
): FeedbackCoordinator { ): FeedbackCoordinator {
return prefs.getBoolean(Const.Pref.FEEDBACK_ENABLED).let { isEnabled -> return prefs.getBoolean(Const.Pref.FEEDBACK_ENABLED).let { isEnabled ->
// observe nothing unless feedback is enabled // observe nothing unless feedback is enabled
Twig.plant(if (isEnabled) TroubleshootingTwig() else SilentTwig()) Twig.plant(if (isEnabled) DebugFileTwig() else SilentTwig())
FeedbackCoordinator(feedback, if (isEnabled) defaultObservers else setOf()) FeedbackCoordinator(feedback, if (isEnabled) defaultObservers else setOf())
} }
} }

View File

@ -2,7 +2,7 @@ package cash.z.ecc.android.ext
object Const { object Const {
/** /**
* Named objects for Dependency Injection * Named objects for Dependency Injection.
*/ */
object Name { object Name {
/** application data other than cryptographic keys */ /** application data other than cryptographic keys */
@ -12,7 +12,7 @@ object Const {
} }
/** /**
* App preference key names * App preference key names.
*/ */
object Pref { object Pref {
const val FIRST_USE_VIEW_TX = "const.pref.first_use_view_tx" const val FIRST_USE_VIEW_TX = "const.pref.first_use_view_tx"
@ -21,6 +21,21 @@ object Const {
const val SERVER_PORT = "const.pref.server_port" const val SERVER_PORT = "const.pref.server_port"
} }
/**
* Constants used for wallet backup.
*/
object Backup {
const val SEED = "cash.z.ecc.android.SEED"
const val SEED_PHRASE = "cash.z.ecc.android.SEED_PHRASE"
const val HAS_SEED = "cash.z.ecc.android.HAS_SEED"
const val HAS_SEED_PHRASE = "cash.z.ecc.android.HAS_SEED_PHRASE"
const val HAS_BACKUP = "cash.z.ecc.android.HAS_BACKUP"
// Config
const val VIEWING_KEY = "cash.z.ecc.android.VIEWING_KEY"
const val BIRTHDAY_HEIGHT = "cash.z.ecc.android.BIRTHDAY_HEIGHT"
}
/** /**
* Default values to use application-wide. Ideally, this set of values should remain very short. * Default values to use application-wide. Ideally, this set of values should remain very short.
*/ */

View File

@ -1,5 +1,8 @@
package cash.z.ecc.android.ext package cash.z.ecc.android.ext
import cash.z.ecc.android.ext.ConversionsUniform.FULL_FORMATTER
import cash.z.ecc.android.ext.ConversionsUniform.LONG_SCALE
import cash.z.ecc.android.ext.ConversionsUniform.SHORT_FORMATTER
import cash.z.ecc.android.sdk.ext.Conversions import cash.z.ecc.android.sdk.ext.Conversions
import cash.z.ecc.android.sdk.ext.ZcashSdk import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.ext.convertZatoshiToZec import cash.z.ecc.android.sdk.ext.convertZatoshiToZec
@ -11,123 +14,66 @@ import java.text.DecimalFormat
import java.text.NumberFormat import java.text.NumberFormat
import java.util.* import java.util.*
/**
* Do the necessary conversions in one place
*
* "1.234" -> to zatoshi
* (zecStringToZatoshi)
* String.toZatoshi()
*
* 123123 -> to "1.2132"
* (zatoshiToZecString)
* Long.toZecString()
*
*/
object ConversionsUniform { object ConversionsUniform {
var ONE_ZEC_IN_ZATOSHI = BigDecimal(ZcashSdk.ZATOSHI_PER_ZEC, MathContext.DECIMAL128) val ONE_ZEC_IN_ZATOSHI = BigDecimal(ZcashSdk.ZATOSHI_PER_ZEC, MathContext.DECIMAL128)
var ZEC_FORMATTER = (NumberFormat.getNumberInstance(Locale("en", "UK")) as DecimalFormat).apply { val LONG_SCALE = 8
applyPattern("###.##") val SHORT_SCALE = 8
roundingMode = RoundingMode.DOWN val SHORT_FORMATTER = from(SHORT_SCALE)
maximumFractionDigits = 6 val FULL_FORMATTER = from(LONG_SCALE)
minimumFractionDigits = 0
val roundingMode = RoundingMode.HALF_EVEN
private fun from(maxDecimals: Int = 8, minDecimals: Int = 0) = (NumberFormat.getNumberInstance(Locale("en", "USA")) as DecimalFormat).apply {
// applyPattern("###.##")
isParseBigDecimal = true
roundingMode = roundingMode
maximumFractionDigits = maxDecimals
minimumFractionDigits = minDecimals
minimumIntegerDigits = 1 minimumIntegerDigits = 1
} }
} }
object WalletZecFormmatter {
/** fun toZatoshi(zecString: String): Long? {
* Format a Zatoshi value into ZEC with the given number of digits, represented as a string. return toBigDecimal(zecString)?.multiply(Conversions.ONE_ZEC_IN_ZATOSHI, MathContext.DECIMAL128)?.toLong()
* Start with Zatoshi -> End with ZEC.
*
* @param maxDecimals the number of decimal places to use in the format. Default is 6 because ZEC is
* better than USD.
* @param minDecimals the minimum number of digits to allow to the right of the decimal.
*
* @return this Zatoshi value represented as ZEC, in a string with at least [minDecimals] and at
* most [maxDecimals]
*/
inline fun Long?.convertZatoshiToZecStringUniform(
maxDecimals: Int = ConversionsUniform.ZEC_FORMATTER.maximumFractionDigits,
minDecimals: Int = ConversionsUniform.ZEC_FORMATTER.minimumFractionDigits
): String {
return currencyFormatterUniform(maxDecimals, minDecimals).format(this.convertZatoshiToZec(maxDecimals))
} }
fun toZecStringShort(zatoshi: Long?): String {
/** return SHORT_FORMATTER.format((zatoshi ?: 0).toZec())
* Format a ZEC value into ZEC with the given number of digits, represented as a string.
* Start with ZEC -> End with ZEC.
*
* @param maxDecimals the number of decimal places to use in the format. Default is 6 because ZEC is
* better when right.
* @param minDecimals the minimum number of digits to allow to the right of the decimal.
*
* @return this Double ZEC value represented as a string with at least [minDecimals] and at most
* [maxDecimals].
*/
inline fun Double?.toZecStringUniform(
maxDecimals: Int = ConversionsUniform.ZEC_FORMATTER.maximumFractionDigits,
minDecimals: Int = ConversionsUniform.ZEC_FORMATTER.minimumFractionDigits
): String {
return currencyFormatterUniform(maxDecimals, minDecimals).format(this.toZec(maxDecimals))
} }
fun toZecStringFull(zatoshi: Long?): String {
/** return formatFull((zatoshi ?: 0).toZec())
* Format a Zatoshi value into ZEC with the given number of decimal places, represented as a string.
* Start with ZeC -> End with ZEC.
*
* @param maxDecimals the number of decimal places to use in the format. Default is 6 because ZEC is
* better than bread.
* @param minDecimals the minimum number of digits to allow to the right of the decimal.
*
* @return this BigDecimal ZEC value represented as a string with at least [minDecimals] and at most
* [maxDecimals].
*/
inline fun BigDecimal?.toZecStringUniform(
maxDecimals: Int = ConversionsUniform.ZEC_FORMATTER.maximumFractionDigits,
minDecimals: Int = ConversionsUniform.ZEC_FORMATTER.minimumFractionDigits
): String {
return currencyFormatterUniform(maxDecimals, minDecimals).format(this.toZecUniform(maxDecimals))
} }
fun formatFull(zec: BigDecimal): String {
/** return FULL_FORMATTER.format(zec)
* Format a Double ZEC value as a BigDecimal ZEC value, right-padded to the given number of fraction
* digits.
* Start with ZEC -> End with ZEC.
*
* @param decimals the scale to use for the resulting BigDecimal.
*
* @return this Double ZEC value converted into a BigDecimal, with the proper rounding mode for use
* with other formatting functions.
*/
inline fun Double?.toZec(decimals: Int = ConversionsUniform.ZEC_FORMATTER.maximumFractionDigits): BigDecimal {
return BigDecimal(this?.toString() ?: "0.0", MathContext.DECIMAL128).setScale(
decimals,
ConversionsUniform.ZEC_FORMATTER.roundingMode
)
} }
fun toBigDecimal(zecString: String?): BigDecimal? {
/** if (zecString.isNullOrEmpty()) return BigDecimal.ZERO
* Format a BigDecimal ZEC value as a BigDecimal ZEC value, right-padded to the given number of return try {
* fraction digits. // ignore commas and whitespace
* Start with ZEC -> End with ZEC. var sanitizedInput = zecString.filter { it.isDigit() or (it == '.') }
* BigDecimal.ZERO.max(FULL_FORMATTER.parse(sanitizedInput) as BigDecimal)
* @param decimals the scale to use for the resulting BigDecimal. } catch (t: Throwable) {
* return null
* @return this BigDecimal ZEC adjusted to the default scale and rounding mode.
*/
inline fun BigDecimal?.toZecUniform(decimals: Int = ConversionsUniform.ZEC_FORMATTER.maximumFractionDigits): BigDecimal {
return (this ?: BigDecimal.ZERO).setScale(decimals, ConversionsUniform.ZEC_FORMATTER.roundingMode)
}
/**
* Create a number formatter for use with converting currency to strings. This probably isn't needed
* externally since the other formatting functions leverage this, instead. Leverages the default
* rounding mode for ZEC found in ZEC_FORMATTER.
*
* @param maxDecimals the number of decimal places to use in the format. Default is 6 because ZEC is
* glorious.
* @param minDecimals the minimum number of digits to allow to the right of the decimal.
*
* @return a currency formatter, appropriate for the default locale.
*/
inline fun currencyFormatterUniform(maxDecimals: Int, minDecimals: Int): DecimalFormat {
return (ConversionsUniform.ZEC_FORMATTER.clone() as DecimalFormat).apply {
maximumFractionDigits = maxDecimals
minimumFractionDigits = minDecimals
} }
} }
/** // convert a zatoshi value to ZEC as a BigDecimal
* Checks if the decimal separator is the last symbol private fun Long?.toZec(): BigDecimal =
*/ BigDecimal(this ?: 0L, MathContext.DECIMAL128)
inline fun String.endsWithDecimalSeparator(): Boolean { .divide(ConversionsUniform.ONE_ZEC_IN_ZATOSHI)
return this.endsWith(ConversionsUniform.ZEC_FORMATTER.decimalFormatSymbols.toString()) .setScale(LONG_SCALE, ConversionsUniform.roundingMode)
} }

View File

@ -3,26 +3,22 @@ package cash.z.ecc.android.ext
import android.app.ActivityManager import android.app.ActivityManager
import android.app.Dialog import android.app.Dialog
import android.content.Context import android.content.Context
import android.content.Intent
import android.provider.Settings
import android.view.View
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import cash.z.ecc.android.sdk.exception.LightWalletException import cash.z.ecc.android.R
import cash.z.ecc.android.ui.MainActivity
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
fun Context.showClearDataConfirmation(onDismiss: () -> Unit = {}, onCancel: () -> Unit = {}): Dialog { fun Context.showClearDataConfirmation(onDismiss: () -> Unit = {}, onCancel: () -> Unit = {}): Dialog {
return MaterialAlertDialogBuilder(this) return MaterialAlertDialogBuilder(this)
.setTitle("Nuke Wallet?") .setTitle(R.string.dialog_nuke_wallet_title)
.setMessage("WARNING: Potential Loss of Funds\n\nClearing all wallet data and can result in a loss of funds, if you cannot locate your correct seed phrase.\n\nPlease confirm that you have your 24-word seed phrase available before proceeding.") .setMessage(R.string.dialog_nuke_wallet_message)
.setCancelable(false) .setCancelable(false)
.setPositiveButton("Cancel") { dialog, _ -> .setPositiveButton(R.string.dialog_nuke_wallet_button_positive) { dialog, _ ->
dialog.dismiss() dialog.dismiss()
onDismiss() onDismiss()
onCancel() onCancel()
} }
.setNegativeButton("Erase Wallet") { dialog, _ -> .setNegativeButton(R.string.dialog_nuke_wallet_button_negative) { dialog, _ ->
dialog.dismiss() dialog.dismiss()
onDismiss() onDismiss()
getSystemService<ActivityManager>()?.clearApplicationUserData() getSystemService<ActivityManager>()?.clearApplicationUserData()
@ -32,15 +28,15 @@ fun Context.showClearDataConfirmation(onDismiss: () -> Unit = {}, onCancel: () -
fun Context.showUninitializedError(error: Throwable? = null, onDismiss: () -> Unit = {}): Dialog { fun Context.showUninitializedError(error: Throwable? = null, onDismiss: () -> Unit = {}): Dialog {
return MaterialAlertDialogBuilder(this) return MaterialAlertDialogBuilder(this)
.setTitle("Wallet Improperly Initialized") .setTitle(R.string.dialog_error_uninitialized_title)
.setMessage("This wallet has not been initialized correctly! Perhaps an error occurred during install.\n\nThis can be fixed with a reset. First, locate your backup seed phrase, then CLEAR DATA and reimport it.") .setMessage(R.string.dialog_error_uninitialized_message)
.setCancelable(false) .setCancelable(false)
.setPositiveButton("Exit") { dialog, _ -> .setPositiveButton(getString(R.string.dialog_error_uninitialized_button_positive)) { dialog, _ ->
dialog.dismiss() dialog.dismiss()
onDismiss() onDismiss()
if (error != null) throw error if (error != null) throw error
} }
.setNegativeButton("Clear Data") { dialog, _ -> .setNegativeButton(getString(R.string.dialog_error_uninitialized_button_negative)) { dialog, _ ->
showClearDataConfirmation(onDismiss, onCancel = { showClearDataConfirmation(onDismiss, onCancel = {
// do not let the user back into the app because we cannot recover from this case // do not let the user back into the app because we cannot recover from this case
showUninitializedError(error, onDismiss) showUninitializedError(error, onDismiss)
@ -51,10 +47,10 @@ fun Context.showUninitializedError(error: Throwable? = null, onDismiss: () -> Un
fun Context.showInvalidSeedPhraseError(error: Throwable? = null, onDismiss: () -> Unit = {}): Dialog { fun Context.showInvalidSeedPhraseError(error: Throwable? = null, onDismiss: () -> Unit = {}): Dialog {
return MaterialAlertDialogBuilder(this) return MaterialAlertDialogBuilder(this)
.setTitle("Oops! Invalid Seed Phrase") .setTitle(R.string.dialog_error_invalid_seed_phrase_title)
.setMessage("That seed phrase appears to be invalid! Please double-check it and try again.\n\n${error?.message ?: ""}") .setMessage(getString(R.string.dialog_error_invalid_seed_phrase_message, error?.message ?: ""))
.setCancelable(false) .setCancelable(false)
.setPositiveButton("Retry") { dialog, _ -> .setPositiveButton(getString(R.string.dialog_error_invalid_seed_phrase_button_positive)) { dialog, _ ->
dialog.dismiss() dialog.dismiss()
onDismiss() onDismiss()
} }
@ -68,14 +64,14 @@ fun Context.showScanFailure(error: Throwable?, onCancel: () -> Unit = {}, onDism
"${error.message}${if (error.cause != null) "\n\nCaused by: ${error.cause}" else ""}" "${error.message}${if (error.cause != null) "\n\nCaused by: ${error.cause}" else ""}"
} }
return MaterialAlertDialogBuilder(this) return MaterialAlertDialogBuilder(this)
.setTitle("Scan Failure") .setTitle(R.string.dialog_error_scan_failure_title)
.setMessage(message) .setMessage(message)
.setCancelable(true) .setCancelable(true)
.setPositiveButton("Retry") { d, _ -> .setPositiveButton(R.string.dialog_error_scan_failure_button_positive) { d, _ ->
d.dismiss() d.dismiss()
onDismiss() onDismiss()
} }
.setNegativeButton("Ignore") { d, _ -> .setNegativeButton(R.string.dialog_error_scan_failure_button_negative) { d, _ ->
d.dismiss() d.dismiss()
onCancel() onCancel()
onDismiss() onDismiss()
@ -85,14 +81,14 @@ fun Context.showScanFailure(error: Throwable?, onCancel: () -> Unit = {}, onDism
fun Context.showCriticalProcessorError(error: Throwable?, onRetry: () -> Unit = {}): Dialog { fun Context.showCriticalProcessorError(error: Throwable?, onRetry: () -> Unit = {}): Dialog {
return MaterialAlertDialogBuilder(this) return MaterialAlertDialogBuilder(this)
.setTitle("Processor Error") .setTitle(R.string.dialog_error_processor_critical_title)
.setMessage(error?.message ?: "Critical error while processing blocks!") .setMessage(error?.message ?: getString(R.string.dialog_error_processor_critical_message))
.setCancelable(false) .setCancelable(false)
.setPositiveButton("Retry") { d, _ -> .setPositiveButton(R.string.dialog_error_processor_critical_button_positive) { d, _ ->
d.dismiss() d.dismiss()
onRetry() onRetry()
} }
.setNegativeButton("Exit") { dialog, _ -> .setNegativeButton(R.string.dialog_error_processor_critical_button_negative) { dialog, _ ->
dialog.dismiss() dialog.dismiss()
throw error ?: RuntimeException("Critical error while processing blocks and the user chose to exit.") throw error ?: RuntimeException("Critical error while processing blocks and the user chose to exit.")
} }
@ -101,26 +97,26 @@ fun Context.showCriticalProcessorError(error: Throwable?, onRetry: () -> Unit =
fun Context.showUpdateServerCriticalError(userFacingMessage: String, onConfirm: () -> Unit = {}): Dialog { fun Context.showUpdateServerCriticalError(userFacingMessage: String, onConfirm: () -> Unit = {}): Dialog {
return MaterialAlertDialogBuilder(this) return MaterialAlertDialogBuilder(this)
.setTitle("Failed to Change Server") .setTitle(R.string.dialog_error_change_server_title)
.setMessage(userFacingMessage) .setMessage(userFacingMessage)
.setCancelable(false) .setCancelable(false)
.setPositiveButton("Ok") { d, _ -> .setPositiveButton(R.string.dialog_error_change_server_button_positive) { d, _ ->
d.dismiss() d.dismiss()
onConfirm() onConfirm()
} }
.show() .show()
} }
fun Context.showUpdateServerDialog(positiveText: String = "Update", onCancel: () -> Unit = {}, onUpdate: () -> Unit = {}): Dialog { fun Context.showUpdateServerDialog(positiveResId: Int = R.string.dialog_modify_server_button_positive, onCancel: () -> Unit = {}, onUpdate: () -> Unit = {}): Dialog {
return MaterialAlertDialogBuilder(this) return MaterialAlertDialogBuilder(this)
.setTitle("Modify Lightwalletd Server?") .setTitle(R.string.dialog_modify_server_title)
.setMessage("WARNING: Entering an invalid or untrusted server might result in misconfiguration or loss of funds!") .setMessage(R.string.dialog_modify_server_message)
.setCancelable(false) .setCancelable(false)
.setPositiveButton(positiveText) { dialog, _ -> .setPositiveButton(positiveResId) { dialog, _ ->
dialog.dismiss() dialog.dismiss()
onUpdate() onUpdate()
} }
.setNegativeButton("Cancel") { dialog, _ -> .setNegativeButton(R.string.dialog_modify_server_button_negative) { dialog, _ ->
dialog.dismiss() dialog.dismiss()
onCancel onCancel
} }

View File

@ -42,7 +42,7 @@ inline fun EditText.limitDecimalPlaces(max: Int) {
if (editText.selectionStart == editText.selectionEnd && editText.selectionStart != textStr.length) { if (editText.selectionStart == editText.selectionEnd && editText.selectionStart != textStr.length) {
textStr = previousValue textStr = previousValue
} else { } else {
textStr = number.toZecStringUniform(8) textStr = WalletZecFormmatter.formatFull(number)
} }
} }

View File

@ -1,6 +1,13 @@
package cash.z.ecc.android.ext package cash.z.ecc.android.ext
import android.content.Context
import android.os.Build
import androidx.fragment.app.Fragment
import cash.z.ecc.android.sdk.ext.Bush
import cash.z.ecc.android.sdk.ext.CompositeTwig
import cash.z.ecc.android.sdk.ext.Twig
import cash.z.ecc.android.sdk.ext.twig import cash.z.ecc.android.sdk.ext.twig
import java.util.*
fun Boolean.asString(ifTrue: String = "", ifFalse: String = "") = if (this) ifTrue else ifFalse fun Boolean.asString(ifTrue: String = "", ifFalse: String = "") = if (this) ifTrue else ifFalse
@ -12,3 +19,20 @@ inline fun <R> tryWithWarning(message: String = "", block: () -> R): R? {
null null
} }
} }
inline fun Fragment.locale(): Locale = context?.locale() ?: Locale.getDefault()
inline fun Context.locale(): Locale {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
resources.configuration.locales.get(0)
} else {
//noinspection deprecation
resources.configuration.locale
}
}
// TODO: add this to the SDK and if the trunk is a CompositeTwig, search through there before returning null
inline fun <reified T> Twig.find(): T? {
return if (Bush.trunk::class.java.isAssignableFrom(T::class.java)) Bush.trunk as T
else null
}

View File

@ -19,8 +19,11 @@ internal inline fun @receiver:ColorRes Int.toAppColor(): Int {
/** /**
* Grab a string from the application resources * Grab a string from the application resources
*/ */
internal inline fun @receiver:StringRes Int.toAppString(): String { internal inline fun @receiver:StringRes Int.toAppString(lowercase: Boolean = false): String {
return ZcashWalletApp.instance.getString(this)} return ZcashWalletApp.instance.getString(this).let {
if (lowercase) it.toLowerCase() else it
}
}
/** /**

View File

@ -38,7 +38,9 @@ import cash.z.ecc.android.databinding.DialogFirstUseMessageBinding
import cash.z.ecc.android.di.component.MainActivitySubcomponent import cash.z.ecc.android.di.component.MainActivitySubcomponent
import cash.z.ecc.android.di.component.SynchronizerSubcomponent import cash.z.ecc.android.di.component.SynchronizerSubcomponent
import cash.z.ecc.android.di.viewmodel.activityViewModel import cash.z.ecc.android.di.viewmodel.activityViewModel
import cash.z.ecc.android.ext.Const import cash.z.ecc.android.ext.showCriticalProcessorError
import cash.z.ecc.android.ext.showScanFailure
import cash.z.ecc.android.ext.showUninitializedError
import cash.z.ecc.android.feedback.Feedback import cash.z.ecc.android.feedback.Feedback
import cash.z.ecc.android.feedback.FeedbackCoordinator import cash.z.ecc.android.feedback.FeedbackCoordinator
import cash.z.ecc.android.feedback.LaunchMetric import cash.z.ecc.android.feedback.LaunchMetric
@ -247,6 +249,9 @@ class MainActivity : AppCompatActivity() {
} }
else -> { else -> {
twig("Warning: failed authentication because $errString [$errorCode]") twig("Warning: failed authentication because $errString [$errorCode]")
// we get here when a device does not have a pin setup, at all
// in the future, notify the user here that they can set a pin on their device for added security
block()
} }
} }
} }
@ -255,7 +260,7 @@ class MainActivity : AppCompatActivity() {
BiometricPrompt(this, ContextCompat.getMainExecutor(this), callback).apply { BiometricPrompt(this, ContextCompat.getMainExecutor(this), callback).apply {
authenticate( authenticate(
BiometricPrompt.PromptInfo.Builder() BiometricPrompt.PromptInfo.Builder()
.setTitle("Authenticate to Proceed") .setTitle(getString(R.string.biometric_prompt_title))
.setConfirmationRequired(false) .setConfirmationRequired(false)
.setDescription(description) .setDescription(description)
.setDeviceCredentialAllowed(true) .setDeviceCredentialAllowed(true)
@ -331,7 +336,7 @@ class MainActivity : AppCompatActivity() {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show() Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
} }
fun showSnackbar(message: String, action: String = "OK"): Snackbar { fun showSnackbar(message: String, action: String = getString(android.R.string.ok)): Snackbar {
return if (snackbar == null) { return if (snackbar == null) {
val view = findViewById<View>(R.id.main_activity_container) val view = findViewById<View>(R.id.main_activity_container)
val snacks = Snackbar val snacks = Snackbar
@ -410,7 +415,7 @@ class MainActivity : AppCompatActivity() {
} }
private fun onNoCamera() { private fun onNoCamera() {
showSnackbar("Well, this is awkward. You denied permission for the camera.") showSnackbar(getString(R.string.camera_permission_denied))
} }
// TODO: clean up this error handling // TODO: clean up this error handling
@ -422,15 +427,9 @@ class MainActivity : AppCompatActivity() {
if (dialog == null) { if (dialog == null) {
notified = true notified = true
runOnUiThread { runOnUiThread {
dialog = MaterialAlertDialogBuilder(this) dialog = showUninitializedError(error) {
.setTitle("Wallet Improperly Initialized") dialog = null
.setMessage("This wallet has not been initialized correctly! Perhaps an error occurred during install.\n\nThis can be fixed with a reset. Please reimport using your backup seed phrase.")
.setCancelable(false)
.setPositiveButton("Exit") { dialog, _ ->
dialog.dismiss()
throw error
} }
.show()
} }
} }
} }
@ -438,20 +437,10 @@ class MainActivity : AppCompatActivity() {
if (dialog == null && !ignoreScanFailure) throttle("scanFailure", 20_000L) { if (dialog == null && !ignoreScanFailure) throttle("scanFailure", 20_000L) {
notified = true notified = true
runOnUiThread { runOnUiThread {
dialog = MaterialAlertDialogBuilder(this) dialog = showScanFailure(error,
.setTitle("Scan Failure") onCancel = { dialog = null },
.setMessage("${error.message}${if (error.cause != null) "\n\nCaused by: ${error.cause}" else ""}") onDismiss = { dialog = null }
.setCancelable(true) )
.setPositiveButton("Retry") { d, _ ->
d.dismiss()
dialog = null
}
.setNegativeButton("Ignore") { d, _ ->
d.dismiss()
ignoreScanFailure = true
dialog = null
}
.show()
} }
} }
} }
@ -462,20 +451,9 @@ class MainActivity : AppCompatActivity() {
if (dialog == null) { if (dialog == null) {
notified = true notified = true
runOnUiThread { runOnUiThread {
dialog = MaterialAlertDialogBuilder(this) dialog = showCriticalProcessorError(error) {
.setTitle("Processor Error")
.setMessage(error?.message ?: "Critical error while processing blocks!")
.setCancelable(false)
.setPositiveButton("Retry") { d, _ ->
d.dismiss()
dialog = null dialog = null
} }
.setNegativeButton("Exit") { dialog, _ ->
dialog.dismiss()
throw error
?: RuntimeException("Critical error while processing blocks and the user chose to exit.")
}
.show()
} }
} }
} }
@ -604,7 +582,7 @@ class MainActivity : AppCompatActivity() {
try { try {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url))) startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
} catch (t: Throwable) { } catch (t: Throwable) {
Toast.makeText(this, "Failed to open browser.", Toast.LENGTH_LONG).show() Toast.makeText(this, R.string.error_launch_url, Toast.LENGTH_LONG).show()
twig("Warning: failed to open browser due to $t") twig("Warning: failed to open browser due to $t")
} }
} }

View File

@ -10,9 +10,7 @@ import androidx.recyclerview.widget.RecyclerView
import cash.z.ecc.android.R import cash.z.ecc.android.R
import cash.z.ecc.android.databinding.FragmentHistoryBinding import cash.z.ecc.android.databinding.FragmentHistoryBinding
import cash.z.ecc.android.di.viewmodel.activityViewModel import cash.z.ecc.android.di.viewmodel.activityViewModel
import cash.z.ecc.android.ext.goneIf import cash.z.ecc.android.ext.*
import cash.z.ecc.android.ext.onClickNavUp
import cash.z.ecc.android.ext.toColoredSpan
import cash.z.ecc.android.feedback.Report import cash.z.ecc.android.feedback.Report
import cash.z.ecc.android.feedback.Report.Tap.HISTORY_BACK import cash.z.ecc.android.feedback.Report.Tap.HISTORY_BACK
import cash.z.ecc.android.sdk.block.CompactBlockProcessor.WalletBalance import cash.z.ecc.android.sdk.block.CompactBlockProcessor.WalletBalance
@ -44,7 +42,7 @@ class HistoryFragment : BaseFragment<FragmentHistoryBinding>() {
initTransactionUI() initTransactionUI()
binding.backButtonHitArea.onClickNavUp { tapped(HISTORY_BACK) } binding.backButtonHitArea.onClickNavUp { tapped(HISTORY_BACK) }
lifecycleScope.launch { lifecycleScope.launch {
binding.textAddress.text = viewModel.getAddress().toAbbreviatedAddress() binding.textAddress.text = viewModel.getAddress().toAbbreviatedAddress(10, 10)
} }
} }
@ -58,12 +56,13 @@ class HistoryFragment : BaseFragment<FragmentHistoryBinding>() {
} }
private fun onBalanceUpdated(balance: WalletBalance) { private fun onBalanceUpdated(balance: WalletBalance) {
binding.textBalanceAvailable.text = balance.availableZatoshi.convertZatoshiToZecString() binding.textBalanceAvailable.text = WalletZecFormmatter.toZecStringShort(balance.availableZatoshi)
val change = (balance.totalZatoshi - balance.availableZatoshi) val change = (balance.totalZatoshi - balance.availableZatoshi)
binding.textBalanceDescription.apply { binding.textBalanceDescription.apply {
goneIf(change <= 0L) goneIf(change <= 0L)
val changeString = change.convertZatoshiToZecString() val changeString = WalletZecFormmatter.toZecStringFull(change)
text = "(expecting +$changeString ZEC)".toColoredSpan(R.color.text_light, "+${changeString}") val expecting = R.string.home_banner_expecting.toAppString(true)
text = "($expecting +$changeString ZEC)".toColoredSpan(R.color.text_light, "+${changeString}")
} }
} }

View File

@ -18,7 +18,10 @@ import cash.z.ecc.android.di.viewmodel.activityViewModel
import cash.z.ecc.android.ext.* import cash.z.ecc.android.ext.*
import cash.z.ecc.android.feedback.Report import cash.z.ecc.android.feedback.Report
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
import cash.z.ecc.android.sdk.ext.* import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString
import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress
import cash.z.ecc.android.sdk.ext.twig
import cash.z.ecc.android.ui.MainActivity import cash.z.ecc.android.ui.MainActivity
import cash.z.ecc.android.ui.base.BaseFragment import cash.z.ecc.android.ui.base.BaseFragment
import cash.z.ecc.android.ui.util.toUtf8Memo import cash.z.ecc.android.ui.util.toUtf8Memo
@ -116,9 +119,9 @@ class TransactionFragment : BaseFragment<FragmentTransactionBinding>() {
// TODO: remove logic from sections below and add more fields or extension functions to UiModel // TODO: remove logic from sections below and add more fields or extension functions to UiModel
uiModel.confirmation?.let { uiModel.confirmation?.let {
subwaySpotConfirmations.visible(); subwayLabelConfirmations.visible(); subwaySpotConfirmations.visible(); subwayLabelConfirmations.visible()
subwayLabelConfirmations.text = it subwayLabelConfirmations.text = it
if (it.equals("confirmed", true)) { if (it.equals(getString(R.string.transaction_status_confirmed), true)) {
subwayLabelConfirmations.setTextColor(R.color.tx_primary.toAppColor()) subwayLabelConfirmations.setTextColor(R.color.tx_primary.toAppColor())
} else { } else {
subwayLabelConfirmations.setTextColor(R.color.tx_text_light_dimmed.toAppColor()) subwayLabelConfirmations.setTextColor(R.color.tx_text_light_dimmed.toAppColor())
@ -196,13 +199,11 @@ class TransactionFragment : BaseFragment<FragmentTransactionBinding>() {
else -> null else -> null
} }
isMined = tx?.minedHeight != null && tx.minedHeight > ZcashSdk.SAPLING_ACTIVATION_HEIGHT isMined = tx?.minedHeight != null && tx.minedHeight > ZcashSdk.SAPLING_ACTIVATION_HEIGHT
topValue = if (tx == null) "" else "\$${tx?.value.convertZatoshiToZecString()}" topValue = if (tx == null) "" else "\$${WalletZecFormmatter.toZecStringFull(tx.value)}"
minedHeight = NumberFormat.getNumberInstance(Locale.getDefault()).format( minedHeight = (tx?.minedHeight ?: 0).toString()
tx?.minedHeight ?: 0
)
val flags = val flags =
DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_YEAR or DateUtils.FORMAT_ABBREV_MONTH DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_YEAR or DateUtils.FORMAT_ABBREV_MONTH
timestamp = if (tx == null) "Details Unavailable" else DateUtils.getRelativeDateTimeString( timestamp = if (tx == null) getString(R.string.transaction_timestamp_unavailable) else DateUtils.getRelativeDateTimeString(
ZcashWalletApp.instance, ZcashWalletApp.instance,
tx.blockTimeInSeconds * 1000, tx.blockTimeInSeconds * 1000,
DateUtils.SECOND_IN_MILLIS, DateUtils.SECOND_IN_MILLIS,
@ -212,7 +213,7 @@ class TransactionFragment : BaseFragment<FragmentTransactionBinding>() {
// memo logic // memo logic
val txMemo = tx?.memo.toUtf8Memo() val txMemo = tx?.memo.toUtf8Memo()
if (!txMemo.isNullOrEmpty()) { if (!txMemo.isEmpty()) {
memo = txMemo memo = txMemo
} }
@ -224,18 +225,18 @@ class TransactionFragment : BaseFragment<FragmentTransactionBinding>() {
val hasLatestHeight = latestHeight != null && latestHeight > ZcashSdk.SAPLING_ACTIVATION_HEIGHT val hasLatestHeight = latestHeight != null && latestHeight > ZcashSdk.SAPLING_ACTIVATION_HEIGHT
if (it.minedHeight > 0 && hasLatestHeight) { if (it.minedHeight > 0 && hasLatestHeight) {
val confirmations = latestHeight!! - it.minedHeight + 1 val confirmations = latestHeight!! - it.minedHeight + 1
confirmation = if (confirmations > 10) "Confirmed" else "$confirmations of 10 Confirmations" confirmation = if (confirmations >= 10) getString(R.string.transaction_status_confirmed) else "$confirmations ${getString(R.string.transaction_status_confirming)}"
} else { } else {
if (!hasLatestHeight && isSufficientlyOld(tx)) { if (!hasLatestHeight && isSufficientlyOld(tx)) {
twig("Warning: could not load latestheight from server to determine confirmations but this transaction is mined and old enough to be considered confirmed") twig("Warning: could not load latestheight from server to determine confirmations but this transaction is mined and old enough to be considered confirmed")
confirmation = "Confirmed" confirmation = getString(R.string.transaction_status_confirmed)
} else { } else {
twig("Warning: could not determine confirmation text value so it will be left null!") twig("Warning: could not determine confirmation text value so it will be left null!")
confirmation = "Confirmation count temporarily unavailable" confirmation = getString(R.string.transaction_confirmation_count_unavailable)
} }
} }
} else { } else {
confirmation = "Pending" confirmation = getString(R.string.transaction_status_pending)
} }
} }
@ -244,20 +245,20 @@ class TransactionFragment : BaseFragment<FragmentTransactionBinding>() {
// inbound v. outbound values // inbound v. outbound values
when (isInbound) { when (isInbound) {
true -> { true -> {
topLabel = "You Received" topLabel = getString(R.string.transaction_story_inbound)
bottomLabel = "Total Received" bottomLabel = getString(R.string.transaction_story_inbound_total)
bottomValue = "\$${tx?.value.convertZatoshiToZecString()}" bottomValue = "\$${WalletZecFormmatter.toZecStringFull(tx?.value)}"
iconRotation = 315f iconRotation = 315f
source = "to your shielded wallet" source = getString(R.string.transaction_story_to_shielded)
address = mainActivity.extractValidAddress(tx?.memo.toUtf8Memo()) address = mainActivity.extractValidAddress(tx?.memo.toUtf8Memo())
} }
false -> { false -> {
topLabel = "You Sent" topLabel = getString(R.string.transaction_story_outbound)
bottomLabel = "Total Sent" bottomLabel = getString(R.string.transaction_story_outbound_total)
bottomValue = "\$${tx?.value?.plus(ZcashSdk.MINERS_FEE_ZATOSHI).convertZatoshiToZecString()}" bottomValue = "\$${WalletZecFormmatter.toZecStringFull(tx?.value?.plus(ZcashSdk.MINERS_FEE_ZATOSHI))}"
iconRotation = 135f iconRotation = 135f
fee = "+0.0001 network fee" fee = getString(R.string.transaction_story_network_fee, WalletZecFormmatter.toZecStringFull(ZcashSdk.MINERS_FEE_ZATOSHI))
source = "from your shielded wallet" source = getString(R.string.transaction_story_from_shielded)
address = tx?.toAddress address = tx?.toAddress
} }
null -> { null -> {

View File

@ -2,10 +2,14 @@ package cash.z.ecc.android.ui.history
import android.view.View import android.view.View
import android.widget.TextView import android.widget.TextView
import androidx.annotation.StringRes
import androidx.appcompat.content.res.AppCompatResources
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import cash.z.ecc.android.R import cash.z.ecc.android.R
import cash.z.ecc.android.ext.WalletZecFormmatter
import cash.z.ecc.android.ext.goneIf import cash.z.ecc.android.ext.goneIf
import cash.z.ecc.android.ext.locale
import cash.z.ecc.android.ext.toAppColor import cash.z.ecc.android.ext.toAppColor
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
import cash.z.ecc.android.sdk.ext.ZcashSdk import cash.z.ecc.android.sdk.ext.ZcashSdk
@ -16,7 +20,6 @@ import cash.z.ecc.android.ui.MainActivity
import cash.z.ecc.android.ui.util.toUtf8Memo import cash.z.ecc.android.ui.util.toUtf8Memo
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.*
class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : RecyclerView.ViewHolder(itemView) { class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val indicator = itemView.findViewById<View>(R.id.indicator) private val indicator = itemView.findViewById<View>(R.id.indicator)
@ -24,16 +27,16 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
private val topText = itemView.findViewById<TextView>(R.id.text_transaction_top) private val topText = itemView.findViewById<TextView>(R.id.text_transaction_top)
private val bottomText = itemView.findViewById<TextView>(R.id.text_transaction_bottom) private val bottomText = itemView.findViewById<TextView>(R.id.text_transaction_bottom)
private val shieldIcon = itemView.findViewById<View>(R.id.image_shield) private val shieldIcon = itemView.findViewById<View>(R.id.image_shield)
private val formatter = SimpleDateFormat("M/d h:mma", Locale.getDefault()) private val formatter = SimpleDateFormat(itemView.context.getString(R.string.format_date_time), itemView.context.locale())
fun bindTo(transaction: T?) { fun bindTo(transaction: T?) {
val mainActivity = itemView.context as MainActivity val mainActivity = itemView.context as MainActivity
mainActivity.lifecycleScope.launch { mainActivity.lifecycleScope.launch {
// update view // update view
var lineOne: String = "" var lineOne = ""
var lineTwo: String = "" var lineTwo = ""
var amountZec: String = "" var amountZec = ""
var amountDisplay: String = "" var amountDisplay = ""
var amountColor: Int = R.color.text_light_dimmed var amountColor: Int = R.color.text_light_dimmed
var lineOneColor: Int = R.color.text_light var lineOneColor: Int = R.color.text_light
var lineTwoColor: Int = R.color.text_light_dimmed var lineTwoColor: Int = R.color.text_light_dimmed
@ -47,16 +50,16 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
onTransactionLongPressed(this) onTransactionLongPressed(this)
true true
} }
amountZec = value.convertZatoshiToZecString() amountZec = WalletZecFormmatter.toZecStringShort(value)
// TODO: these might be good extension functions // TODO: these might be good extension functions
val timestamp = formatter.format(blockTimeInSeconds * 1000L) val timestamp = formatter.format(blockTimeInSeconds * 1000L)
val isMined = blockTimeInSeconds != 0L val isMined = blockTimeInSeconds != 0L
when { when {
!toAddress.isNullOrEmpty() -> { !toAddress.isNullOrEmpty() -> {
lineOne = "You paid ${toAddress?.toAbbreviatedAddress()}" lineOne = "${str(R.string.transaction_address_you_paid)} ${toAddress?.toAbbreviatedAddress()}"
lineTwo = if (isMined) "Sent $timestamp" else "Pending confirmation" lineTwo = if (isMined) "${str(R.string.transaction_status_sent)} $timestamp" else str(R.string.transaction_status_pending)
// TODO: this logic works but is sloppy. Find a more robust solution to displaying information about expiration (such as expires in 1 block, etc). Then if it is way beyond expired, remove it entirely. Perhaps give the user a button for that (swipe to dismiss?) // TODO: this logic works but is sloppy. Find a more robust solution to displaying information about expiration (such as expires in 1 block, etc). Then if it is way beyond expired, remove it entirely. Perhaps give the user a button for that (swipe to dismiss?)
if(!isMined && (expiryHeight != null) && (expiryHeight!! < mainActivity.latestHeight ?: -1)) lineTwo = "Expired" if(!isMined && (expiryHeight != null) && (expiryHeight!! < mainActivity.latestHeight ?: -1)) lineTwo = str(R.string.transaction_status_expired)
amountDisplay = "- $amountZec" amountDisplay = "- $amountZec"
if (isMined) { if (isMined) {
amountColor = R.color.zcashRed amountColor = R.color.zcashRed
@ -67,23 +70,23 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
} }
} }
toAddress.isNullOrEmpty() && value > 0L && minedHeight > 0 -> { toAddress.isNullOrEmpty() && value > 0L && minedHeight > 0 -> {
lineOne = "${mainActivity.getSender(transaction)} paid you" lineOne = "${mainActivity.getSender(transaction)} ${str(R.string.transaction_address_paid_you)}"
lineTwo = "Received $timestamp" lineTwo = "${str(R.string.transaction_received)} $timestamp"
amountDisplay = "+ $amountZec" amountDisplay = "+ $amountZec"
amountColor = R.color.zcashGreen amountColor = R.color.zcashGreen
indicatorBackground = R.drawable.background_indicator_inbound indicatorBackground = R.drawable.background_indicator_inbound
} }
else -> { else -> {
lineOne = "Unknown" lineOne = str(R.string.unknown)
lineTwo = "Unknown" lineTwo = str(R.string.unknown)
amountDisplay = "$amountZec" amountDisplay = amountZec
amountColor = R.color.text_light amountColor = R.color.text_light
} }
} }
// sanitize amount // sanitize amount
if (value < ZcashSdk.MINERS_FEE_ZATOSHI) amountDisplay = "< 0.001" if (value < ZcashSdk.MINERS_FEE_ZATOSHI) amountDisplay = "< 0.001"
else if (amountZec.length > 10) { // 10 allows 3 digits to the left and 6 to the right of the decimal else if (amountZec.length > 10) { // 10 allows 3 digits to the left and 6 to the right of the decimal
amountDisplay = "tap to view" amountDisplay = str(R.string.transaction_instruction_tap)
} }
} }
@ -94,10 +97,10 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
topText.setTextColor(lineOneColor.toAppColor()) topText.setTextColor(lineOneColor.toAppColor())
bottomText.setTextColor(lineTwoColor.toAppColor()) bottomText.setTextColor(lineTwoColor.toAppColor())
val context = itemView.context val context = itemView.context
indicator.background = context.resources.getDrawable(indicatorBackground) indicator.background = AppCompatResources.getDrawable(itemView.context, indicatorBackground)
// TODO: change this so we see the shield if it is a z-addr in the address line but verify the intended design/behavior, first // TODO: change this so we see the shield if it is a z-addr in the address line but verify the intended design/behavior, first
shieldIcon.goneIf((transaction?.raw != null || transaction?.expiryHeight != null) && !transaction?.toAddress.isShielded()) shieldIcon.goneIf((transaction?.raw != null || transaction?.expiryHeight != null) && !transaction.toAddress.isShielded())
} }
} }
@ -115,6 +118,8 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
} }
} }
private inline fun str(@StringRes resourceId: Int) = itemView.context.getString(resourceId)
} }

View File

@ -5,6 +5,7 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.content.res.AppCompatResources
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import cash.z.ecc.android.R import cash.z.ecc.android.R
import cash.z.ecc.android.databinding.FragmentHomeBinding import cash.z.ecc.android.databinding.FragmentHomeBinding
@ -21,11 +22,9 @@ import cash.z.ecc.android.ui.send.SendViewModel
import cash.z.ecc.android.ui.setup.WalletSetupViewModel import cash.z.ecc.android.ui.setup.WalletSetupViewModel
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.NO_SEED import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.NO_SEED
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.scanReduce
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -65,7 +64,7 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
mainActivity?.safeNavigate(R.id.action_nav_home_to_create_wallet) mainActivity?.safeNavigate(R.id.action_nav_home_to_create_wallet)
} else { } else {
twig("Previous wallet found. Re-opening it.") twig("Previous wallet found. Re-opening it.")
mainActivity?.startSync(walletSetup.buildInitializer()) mainActivity?.startSync(walletSetup.openStoredWallet())
} }
} }
} }
@ -111,7 +110,7 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
if (::uiModel.isInitialized) { if (::uiModel.isInitialized) {
twig("uiModel exists! it has pendingSend=${uiModel.pendingSend} ZEC while the sendViewModel=${sendViewModel.zatoshiAmount} zats") twig("uiModel exists! it has pendingSend=${uiModel.pendingSend} ZEC while the sendViewModel=${sendViewModel.zatoshiAmount} zats")
// if the model already existed, cool but let the sendViewModel be the source of truth for the amount // if the model already existed, cool but let the sendViewModel be the source of truth for the amount
onModelUpdated(null, uiModel.copy(pendingSend = sendViewModel.zatoshiAmount.coerceAtLeast(0).convertZatoshiToZecStringUniform(8))) onModelUpdated(null, uiModel.copy(pendingSend = WalletZecFormmatter.toZecStringFull(sendViewModel.zatoshiAmount.coerceAtLeast(0))))
} }
} }
@ -133,9 +132,9 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
super.onResume() super.onResume()
twig("HomeFragment.onResume resumeScope.isActive: ${resumedScope.isActive} $resumedScope") twig("HomeFragment.onResume resumeScope.isActive: ${resumedScope.isActive} $resumedScope")
val existingAmount = sendViewModel.zatoshiAmount.coerceAtLeast(0) val existingAmount = sendViewModel.zatoshiAmount.coerceAtLeast(0)
viewModel.initializeMaybe(existingAmount.convertZatoshiToZecStringUniform(8)) viewModel.initializeMaybe(WalletZecFormmatter.toZecStringFull(existingAmount))
if (existingAmount == 0L) onClearAmount() if (existingAmount == 0L) onClearAmount()
viewModel.uiModels.scanReduce { old, new -> viewModel.uiModels.runningReduce { old, new ->
onModelUpdated(old, new) onModelUpdated(old, new)
new new
}.onCompletion { }.onCompletion {
@ -157,9 +156,9 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
twig("HomeFragment.onSaveInstanceState") twig("HomeFragment.onSaveInstanceState")
if (::uiModel.isInitialized) { // if (::uiModel.isInitialized) {
// outState.putParcelable("uiModel", uiModel) // outState.putParcelable("uiModel", uiModel)
} // }
} }
override fun onViewStateRestored(savedInstanceState: Bundle?) { override fun onViewStateRestored(savedInstanceState: Bundle?) {
@ -204,20 +203,20 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
} }
val sendText = when { val sendText = when {
uiModel.status == DISCONNECTED -> "Reconnecting . . ." uiModel.status == DISCONNECTED -> getString(R.string.home_button_send_disconnected)
uiModel.isSynced -> if (uiModel.hasFunds) "SEND AMOUNT" else "NO FUNDS AVAILABLE" uiModel.isSynced -> if (uiModel.hasFunds) getString(R.string.home_button_send_has_funds) else getString(R.string.home_button_send_no_funds)
uiModel.status == STOPPED -> "IDLE" uiModel.status == STOPPED -> getString(R.string.home_button_send_idle)
uiModel.isDownloading -> "Downloading . . . ${snake.downloadProgress}%" uiModel.isDownloading -> getString(R.string.home_button_send_downloading, snake.downloadProgress)
uiModel.isValidating -> "Validating . . ." uiModel.isValidating -> getString(R.string.home_button_send_validating)
uiModel.isScanning -> "Scanning . . . ${snake.scanProgress}%" uiModel.isScanning -> getString(R.string.home_button_send_scanning, snake.scanProgress)
else -> "Updating" else -> getString(R.string.home_button_send_updating)
} }
binding.buttonSendAmount.text = sendText binding.buttonSendAmount.text = sendText
twig("Send button set to: $sendText") twig("Send button set to: $sendText")
val resId = if (uiModel.isSynced) R.color.selector_button_text_dark else R.color.selector_button_text_light val resId = if (uiModel.isSynced) R.color.selector_button_text_dark else R.color.selector_button_text_light
binding.buttonSendAmount.setTextColor(resources.getColorStateList(resId)) context?.let { binding.buttonSendAmount.setTextColor(AppCompatResources.getColorStateList(it, resId)) }
binding.lottieButtonLoading.invisibleIf(uiModel.isDisconnected) binding.lottieButtonLoading.invisibleIf(uiModel.isDisconnected)
} }
@ -229,23 +228,24 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
binding.textSendAmount.text = "\$$amount".toColoredSpan(R.color.text_light_dimmed, "$") binding.textSendAmount.text = "\$$amount".toColoredSpan(R.color.text_light_dimmed, "$")
if (updateModel) { if (updateModel) {
sendViewModel.zatoshiAmount = amount.safelyConvertToBigDecimal().convertZecToZatoshi() sendViewModel.zatoshiAmount = amount.safelyConvertToBigDecimal().convertZecToZatoshi()
twig("dBUG: updating model. converting: $amount\tresult: ${sendViewModel.zatoshiAmount}\tprint: ${WalletZecFormmatter.toZecStringFull(sendViewModel.zatoshiAmount)}")
} }
binding.buttonSendAmount.disabledIf(amount == "0") binding.buttonSendAmount.disabledIf(amount == "0")
} }
fun setAvailable(availableBalance: Long = -1L, totalBalance: Long = -1L) { fun setAvailable(availableBalance: Long = -1L, totalBalance: Long = -1L) {
val missingBalance = availableBalance < 0 val missingBalance = availableBalance < 0
val availableString = if (missingBalance) "Updating" else availableBalance.convertZatoshiToZecString() val availableString = if (missingBalance) getString(R.string.home_button_send_updating) else WalletZecFormmatter.toZecStringFull(availableBalance)
binding.textBalanceAvailable.text = availableString binding.textBalanceAvailable.text = availableString
binding.textBalanceAvailable.transparentIf(missingBalance) binding.textBalanceAvailable.transparentIf(missingBalance)
binding.labelBalance.transparentIf(missingBalance) binding.labelBalance.transparentIf(missingBalance)
binding.textBalanceDescription.apply { binding.textBalanceDescription.apply {
goneIf(missingBalance) goneIf(missingBalance)
text = if (availableBalance != -1L && (availableBalance < totalBalance)) { text = if (availableBalance != -1L && (availableBalance < totalBalance)) {
val change = (totalBalance - availableBalance).convertZatoshiToZecString() val change = WalletZecFormmatter.toZecStringFull(totalBalance - availableBalance)
"(expecting +$change ZEC)".toColoredSpan(R.color.text_light, "+$change") "(${getString(R.string.home_banner_expecting)} +$change ZEC)".toColoredSpan(R.color.text_light, "+$change")
} else { } else {
"(enter an amount to send)" getString(R.string.home_instruction_enter_amount)
} }
} }
} }
@ -332,10 +332,10 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
when (action) { when (action) {
FUND_NOW -> { FUND_NOW -> {
MaterialAlertDialogBuilder(activity) MaterialAlertDialogBuilder(activity)
.setMessage("To make full use of this wallet, deposit funds to your address.") .setMessage(R.string.home_dialog_no_balance_message)
.setTitle("No Balance") .setTitle(R.string.home_dialog_no_balance_title)
.setCancelable(true) .setCancelable(true)
.setPositiveButton("View Address") { dialog, _ -> .setPositiveButton(R.string.home_dialog_no_balance_button_positive) { dialog, _ ->
tapped(HOME_FUND_NOW) tapped(HOME_FUND_NOW)
dialog.dismiss() dialog.dismiss()
mainActivity?.safeNavigate(R.id.action_nav_home_to_nav_receive) mainActivity?.safeNavigate(R.id.action_nav_home_to_nav_receive)
@ -363,7 +363,7 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
} }
private fun onNoFunds() { private fun onNoFunds() {
setBanner("No Balance", FUND_NOW) setBanner(getString(R.string.home_no_balance), FUND_NOW)
} }

View File

@ -1,6 +1,8 @@
package cash.z.ecc.android.ui.home package cash.z.ecc.android.ui.home
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import cash.z.ecc.android.R
import cash.z.ecc.android.ext.toAppString
import cash.z.ecc.android.sdk.SdkSynchronizer import cash.z.ecc.android.sdk.SdkSynchronizer
import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.Synchronizer.Status.* import cash.z.ecc.android.sdk.Synchronizer.Status.*
@ -37,25 +39,26 @@ class HomeViewModel @Inject constructor() : ViewModel() {
} }
_typedChars = ConflatedBroadcastChannel() _typedChars = ConflatedBroadcastChannel()
val typedChars = _typedChars.asFlow() val typedChars = _typedChars.asFlow()
val decimal = '.'// R.string.key_decimal.toAppString()[0]
val backspace = R.string.key_backspace.toAppString()[0]
val zec = typedChars.scan(preTypedChars) { acc, c -> val zec = typedChars.scan(preTypedChars) { acc, c ->
when { when {
// no-op cases // no-op cases
acc == "0" && c == '0' acc == "0" && c == '0'
|| (c == '<' && acc == "0") || (c == backspace && acc == "0")
|| (c == '.' && acc.contains('.')) -> {twig("triggered: 1 acc: $acc c: $c") || (c == decimal && acc.contains(decimal)) -> {twig("triggered: 1 acc: $acc c: $c")
acc acc
} }
c == '<' && acc.length <= 1 -> {twig("triggered: 2 $typedChars") c == backspace && acc.length <= 1 -> {twig("triggered: 2 $typedChars")
"0" "0"
} }
c == '<' -> {twig("triggered: 3") c == backspace -> {twig("triggered: 3")
acc.substring(0, acc.length - 1) acc.substring(0, acc.length - 1)
} }
acc == "0" && c != '.' -> {twig("triggered: 4 $typedChars") acc == "0" && c != decimal -> {twig("triggered: 4 $typedChars")
c.toString() c.toString()
} }
acc.contains('.') && acc.length - acc.indexOf('.') > 8 -> { acc.contains(decimal) && acc.length - acc.indexOf(decimal) > 8 -> {
twig("triggered: 5 $typedChars") twig("triggered: 5 $typedChars")
acc acc
} }

View File

@ -7,6 +7,7 @@ import android.view.ViewTreeObserver
import android.view.WindowManager import android.view.WindowManager
import android.widget.Toast import android.widget.Toast
import androidx.core.view.doOnLayout import androidx.core.view.doOnLayout
import cash.z.ecc.android.R
import cash.z.ecc.android.databinding.FragmentFeedbackBinding import cash.z.ecc.android.databinding.FragmentFeedbackBinding
import cash.z.ecc.android.di.viewmodel.viewModel import cash.z.ecc.android.di.viewmodel.viewModel
import cash.z.ecc.android.feedback.Report import cash.z.ecc.android.feedback.Report
@ -67,7 +68,7 @@ class FeedbackFragment : BaseFragment<FragmentFeedbackBinding>() {
// //
private fun onFeedbackSubmit(view: View) { private fun onFeedbackSubmit(view: View) {
Toast.makeText(mainActivity, "Thanks for the feedback!", Toast.LENGTH_LONG).show() Toast.makeText(mainActivity, R.string.feedback_thanks, Toast.LENGTH_LONG).show()
tapped(FEEDBACK_SUBMIT) tapped(FEEDBACK_SUBMIT)
val q1 = binding.inputQuestion1.editText?.text.toString() val q1 = binding.inputQuestion1.editText?.text.toString()

View File

@ -11,6 +11,7 @@ import cash.z.ecc.android.R
import cash.z.ecc.android.ZcashWalletApp import cash.z.ecc.android.ZcashWalletApp
import cash.z.ecc.android.databinding.FragmentProfileBinding import cash.z.ecc.android.databinding.FragmentProfileBinding
import cash.z.ecc.android.di.viewmodel.viewModel import cash.z.ecc.android.di.viewmodel.viewModel
import cash.z.ecc.android.ext.find
import cash.z.ecc.android.ext.onClick import cash.z.ecc.android.ext.onClick
import cash.z.ecc.android.ext.onClickNavBack import cash.z.ecc.android.ext.onClickNavBack
import cash.z.ecc.android.ext.onClickNavTo import cash.z.ecc.android.ext.onClickNavTo
@ -18,14 +19,12 @@ import cash.z.ecc.android.feedback.FeedbackFile
import cash.z.ecc.android.feedback.Report import cash.z.ecc.android.feedback.Report
import cash.z.ecc.android.feedback.Report.Funnel.UserFeedback import cash.z.ecc.android.feedback.Report.Funnel.UserFeedback
import cash.z.ecc.android.feedback.Report.Tap.* import cash.z.ecc.android.feedback.Report.Tap.*
import cash.z.ecc.android.ui.base.BaseFragment import cash.z.ecc.android.sdk.ext.Bush
import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress
import cash.z.ecc.android.sdk.ext.twig import cash.z.ecc.android.ui.base.BaseFragment
import cash.z.ecc.android.ui.util.DebugFileTwig
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import okio.Okio
import java.io.File import java.io.File
import java.io.IOException
import java.lang.IllegalArgumentException
class ProfileFragment : BaseFragment<FragmentProfileBinding>() { class ProfileFragment : BaseFragment<FragmentProfileBinding>() {
@ -70,7 +69,13 @@ class ProfileFragment : BaseFragment<FragmentProfileBinding>() {
} }
private fun onViewDevLogs() { private fun onViewDevLogs() {
shareFile(writeLogcat()) developerLogFile().let {
if (it == null) {
mainActivity?.showSnackbar("Error: No developer log found!")
} else {
shareFile(it)
}
}
} }
private fun shareFiles(vararg files: File?) { private fun shareFiles(vararg files: File?) {
@ -85,7 +90,7 @@ class ProfileFragment : BaseFragment<FragmentProfileBinding>() {
putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris) putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
type = "text/*" type = "text/*"
} }
startActivity(Intent.createChooser(intent, "Share Log Files")) startActivity(Intent.createChooser(intent, getString(R.string.profile_share_log_title)))
} }
fun shareFile(file: File?) { fun shareFile(file: File?) {
@ -95,36 +100,14 @@ class ProfileFragment : BaseFragment<FragmentProfileBinding>() {
putExtra(Intent.EXTRA_STREAM, uri) putExtra(Intent.EXTRA_STREAM, uri)
type = "text/plain" type = "text/plain"
} }
startActivity(Intent.createChooser(intent, "Share Log File")) startActivity(Intent.createChooser(intent, getString(R.string.profile_share_log_title)))
} }
private fun userLogFile(): File? { private fun userLogFile(): File? {
return mainActivity?.feedbackCoordinator?.findObserver<FeedbackFile>()?.file return mainActivity?.feedbackCoordinator?.findObserver<FeedbackFile>()?.file
} }
private fun loadLogFileAsText(): String? { private fun developerLogFile(): File? {
val feedbackFile: File = userLogFile() ?: return null return Bush.trunk.find<DebugFileTwig>()?.file
Okio.buffer(Okio.source(feedbackFile)).use {
return it.readUtf8()
}
}
private fun writeLogcat(): File? {
try {
// Note: the /logs directory has been configured as a file provider under @xml/file_paths which allows the temporary sharing of this file
val outputFile = File("${ZcashWalletApp.instance.filesDir}/logs", "developer_log.txt").also { it.parentFile.mkdirs() }
if (!outputFile.parentFile.isDirectory) {
// addresses security finding in issue #121
throw IllegalArgumentException("Invalid path: ${outputFile.parentFile}. Verify" +
" that the default files directory is not being manipulated.")
}
val cmd = arrayOf("/bin/sh", "-c", "logcat -v time -d | grep '@TWIG' > '${outputFile.absolutePath}'")
Runtime.getRuntime().exec(cmd)
return outputFile
} catch (e: IOException) {
e.printStackTrace()
twig("Failed to create log")
}
return null
} }
} }

View File

@ -14,7 +14,6 @@ import cash.z.ecc.android.databinding.FragmentScanBinding
import cash.z.ecc.android.di.viewmodel.activityViewModel import cash.z.ecc.android.di.viewmodel.activityViewModel
import cash.z.ecc.android.di.viewmodel.viewModel import cash.z.ecc.android.di.viewmodel.viewModel
import cash.z.ecc.android.ext.onClickNavBack import cash.z.ecc.android.ext.onClickNavBack
import cash.z.ecc.android.ext.onClickNavTo
import cash.z.ecc.android.feedback.Report import cash.z.ecc.android.feedback.Report
import cash.z.ecc.android.feedback.Report.Tap.SCAN_BACK import cash.z.ecc.android.feedback.Report.Tap.SCAN_BACK
import cash.z.ecc.android.sdk.ext.ZcashSdk import cash.z.ecc.android.sdk.ext.ZcashSdk
@ -47,8 +46,8 @@ class ScanFragment : BaseFragment<FragmentScanBinding>() {
binding.backButtonHitArea.onClickNavBack() { tapped(SCAN_BACK) } binding.backButtonHitArea.onClickNavBack() { tapped(SCAN_BACK) }
} }
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onCreate(savedInstanceState)
if (!allPermissionsGranted()) getRuntimePermissions() if (!allPermissionsGranted()) getRuntimePermissions()
} }
@ -125,7 +124,7 @@ class ScanFragment : BaseFragment<FragmentScanBinding>() {
resumedScope.launch { resumedScope.launch {
if (viewModel.isNotValid(qrContent)) { if (viewModel.isNotValid(qrContent)) {
val network = ZcashSdk.NETWORK val network = ZcashSdk.NETWORK
binding.textScanError.text = "Invalid Zcash $network address:\n$qrContent" binding.textScanError.text = getString(R.string.scan_invalid_address, network, qrContent)
image.close() image.close()
} else { /* continue scanning*/ } else { /* continue scanning*/
binding.textScanError.text = "" binding.textScanError.text = ""

View File

@ -52,7 +52,7 @@ class SendAddressFragment : BaseFragment<FragmentSendAddressBinding>(),
// Apply View Model // Apply View Model
if (sendViewModel.zatoshiAmount > 0L) { if (sendViewModel.zatoshiAmount > 0L) {
sendViewModel.zatoshiAmount.convertZatoshiToZecStringUniform(8).let { amount -> WalletZecFormmatter.toZecStringFull(sendViewModel.zatoshiAmount).let { amount ->
binding.inputZcashAmount.setText(amount) binding.inputZcashAmount.setText(amount)
} }
} else { } else {
@ -123,7 +123,7 @@ class SendAddressFragment : BaseFragment<FragmentSendAddressBinding>(),
private fun onMax() { private fun onMax() {
if (maxZatoshi != null) { if (maxZatoshi != null) {
binding.inputZcashAmount.apply { binding.inputZcashAmount.apply {
setText(maxZatoshi.convertZatoshiToZecString(8)) setText(WalletZecFormmatter.toZecStringFull(maxZatoshi ?: 0L))
postDelayed({ postDelayed({
requestFocus() requestFocus()
setSelection(text?.length ?: 0) setSelection(text?.length ?: 0)
@ -156,7 +156,7 @@ class SendAddressFragment : BaseFragment<FragmentSendAddressBinding>(),
private fun onBalanceUpdated(balance: WalletBalance) { private fun onBalanceUpdated(balance: WalletBalance) {
binding.textLayoutAmount.helperText = binding.textLayoutAmount.helperText =
"You have ${balance.availableZatoshi.coerceAtLeast(0L).convertZatoshiToZecString(8)} available" "You have ${WalletZecFormmatter.toZecStringFull(balance.availableZatoshi.coerceAtLeast(0L))} available"
maxZatoshi = (balance.availableZatoshi - ZcashSdk.MINERS_FEE_ZATOSHI).coerceAtLeast(0L) maxZatoshi = (balance.availableZatoshi - ZcashSdk.MINERS_FEE_ZATOSHI).coerceAtLeast(0L)
} }

View File

@ -7,6 +7,7 @@ import androidx.lifecycle.lifecycleScope
import cash.z.ecc.android.R import cash.z.ecc.android.R
import cash.z.ecc.android.databinding.FragmentSendConfirmBinding import cash.z.ecc.android.databinding.FragmentSendConfirmBinding
import cash.z.ecc.android.di.viewmodel.activityViewModel import cash.z.ecc.android.di.viewmodel.activityViewModel
import cash.z.ecc.android.ext.WalletZecFormmatter
import cash.z.ecc.android.ext.goneIf import cash.z.ecc.android.ext.goneIf
import cash.z.ecc.android.ext.onClickNavTo import cash.z.ecc.android.ext.onClickNavTo
import cash.z.ecc.android.feedback.Report import cash.z.ecc.android.feedback.Report
@ -36,7 +37,7 @@ class SendConfirmFragment : BaseFragment<FragmentSendConfirmBinding>() {
// } // }
mainActivity?.lifecycleScope?.launch { mainActivity?.lifecycleScope?.launch {
binding.textConfirmation.text = binding.textConfirmation.text =
"Send ${sendViewModel.zatoshiAmount.convertZatoshiToZecString(8)} ZEC to ${sendViewModel?.toAddress.toAbbreviatedAddress()}?" "Send ${WalletZecFormmatter.toZecStringFull(sendViewModel.zatoshiAmount)} ZEC to ${sendViewModel?.toAddress.toAbbreviatedAddress()}?"
} }
sendViewModel.memo.trim().isNotEmpty().let { hasMemo -> sendViewModel.memo.trim().isNotEmpty().let { hasMemo ->
binding.radioIncludeAddress.isChecked = hasMemo || sendViewModel.includeFromAddress binding.radioIncludeAddress.isChecked = hasMemo || sendViewModel.includeFromAddress

View File

@ -1,5 +1,6 @@
package cash.z.ecc.android.ui.send package cash.z.ecc.android.ui.send
import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
@ -8,6 +9,7 @@ import androidx.lifecycle.lifecycleScope
import cash.z.ecc.android.R import cash.z.ecc.android.R
import cash.z.ecc.android.databinding.FragmentSendFinalBinding import cash.z.ecc.android.databinding.FragmentSendFinalBinding
import cash.z.ecc.android.di.viewmodel.activityViewModel import cash.z.ecc.android.di.viewmodel.activityViewModel
import cash.z.ecc.android.ext.WalletZecFormmatter
import cash.z.ecc.android.ext.goneIf import cash.z.ecc.android.ext.goneIf
import cash.z.ecc.android.feedback.Report import cash.z.ecc.android.feedback.Report
import cash.z.ecc.android.feedback.Report.Tap.SEND_FINAL_CLOSE import cash.z.ecc.android.feedback.Report.Tap.SEND_FINAL_CLOSE
@ -23,11 +25,12 @@ import kotlinx.coroutines.flow.onEach
class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() { class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
override val screen = Report.Screen.SEND_FINAL override val screen = Report.Screen.SEND_FINAL
val sendViewModel: SendViewModel by activityViewModel() private val sendViewModel: SendViewModel by activityViewModel()
override fun inflate(inflater: LayoutInflater): FragmentSendFinalBinding = override fun inflate(inflater: LayoutInflater): FragmentSendFinalBinding =
FragmentSendFinalBinding.inflate(inflater) FragmentSendFinalBinding.inflate(inflater)
@SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding.buttonPrimary.setOnClickListener { binding.buttonPrimary.setOnClickListener {
@ -40,7 +43,7 @@ class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
onExit().also { tapped(SEND_FINAL_CLOSE) } onExit().also { tapped(SEND_FINAL_CLOSE) }
} }
binding.textConfirmation.text = binding.textConfirmation.text =
"Sending ${sendViewModel.zatoshiAmount.convertZatoshiToZecString(8)} ZEC to\n${sendViewModel.toAddress.toAbbreviatedAddress()}" "${getString(R.string.send_final_sending)} ${WalletZecFormmatter.toZecStringFull(sendViewModel.zatoshiAmount)} ZEC\n${getString(R.string.send_final_to)}\n${sendViewModel.toAddress.toAbbreviatedAddress()}"
mainActivity?.preventBackPress(this) mainActivity?.preventBackPress(this)
} }
@ -88,7 +91,7 @@ class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
private fun onExit() { private fun onExit() {
sendViewModel.reset() sendViewModel.reset()
mainActivity?.navController?.popBackStack(R.id.nav_home, false) mainActivity?.safeNavigate(R.id.action_nav_send_final_to_nav_home)
} }
private fun onCancel(tx: PendingTransaction) { private fun onCancel(tx: PendingTransaction) {
@ -96,7 +99,7 @@ class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
} }
private fun onReturnToSend() { private fun onReturnToSend() {
mainActivity?.navController?.popBackStack(R.id.nav_send, false) mainActivity?.safeNavigate(R.id.action_nav_send_final_to_nav_send)
} }
private fun onSeeDetails() { private fun onSeeDetails() {
@ -107,30 +110,31 @@ class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
private fun PendingTransaction.toUiModel() = UiModel().also { model -> private fun PendingTransaction.toUiModel() = UiModel().also { model ->
when { when {
isCancelled() -> { isCancelled() -> {
model.title = "Cancelled." model.title = getString(R.string.send_final_result_cancelled)
model.primaryButtonText = "Go Back" model.primaryButtonText = getString(R.string.send_final_button_primary_back)
model.primaryAction = { onReturnToSend() } model.primaryAction = { onReturnToSend() }
} }
isSubmitSuccess() -> { isSubmitSuccess() -> {
model.title = "SENT!" model.title = getString(R.string.send_final_button_primary_sent)
model.primaryButtonText = "See Details" model.primaryButtonText = getString(R.string.send_final_button_primary_details)
model.primaryAction = { onSeeDetails() } model.primaryAction = { onSeeDetails() }
} }
isFailure() -> { isFailure() -> {
model.title = "Failed." model.title = getString(R.string.send_final_button_primary_failed)
model.errorMessage = if (isFailedEncoding()) "The transaction could not be encoded." else "Unable to submit transaction to the network." model.errorMessage = if (isFailedEncoding()) getString(R.string.send_final_error_encoding) else getString(
model.primaryButtonText = "Retry" R.string.send_final_error_submitting)
model.primaryButtonText = getString(R.string.send_final_button_primary_retry)
model.primaryAction = { onReturnToSend() } model.primaryAction = { onReturnToSend() }
} }
else -> { else -> {
model.title = "Sending ${value.convertZatoshiToZecString(8)} ZEC to\n${toAddress.toAbbreviatedAddress()}" model.title = "${getString(R.string.send_final_sending)} ${WalletZecFormmatter.toZecStringFull(value)} ZEC ${getString(R.string.send_final_to)}\n${toAddress.toAbbreviatedAddress()}"
model.showProgress = true model.showProgress = true
if (isCreating()) { if (isCreating()) {
model.showCloseIcon = false model.showCloseIcon = false
model.primaryButtonText = "Cancel" model.primaryButtonText = getString(R.string.send_final_button_primary_cancel)
model.primaryAction = { onCancel(this) } model.primaryAction = { onCancel(this) }
} else { } else {
model.primaryButtonText = "See Details" model.primaryButtonText = getString(R.string.send_final_button_primary_details)
model.primaryAction = { onSeeDetails() } model.primaryAction = { onSeeDetails() }
} }
} }

View File

@ -14,6 +14,7 @@ import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.ImageViewCompat import androidx.core.widget.ImageViewCompat
import androidx.core.widget.doAfterTextChanged import androidx.core.widget.doAfterTextChanged
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import cash.z.ecc.android.R import cash.z.ecc.android.R
import cash.z.ecc.android.databinding.FragmentSendBinding import cash.z.ecc.android.databinding.FragmentSendBinding
@ -105,7 +106,7 @@ class SendFragment : BaseFragment<FragmentSendBinding>(),
private fun applyViewModel(model: SendViewModel) { private fun applyViewModel(model: SendViewModel) {
// apply amount // apply amount
val roundedAmount = val roundedAmount =
model.zatoshiAmount.coerceAtLeast(0L).convertZatoshiToZecStringUniform(8) WalletZecFormmatter.toZecStringFull(model.zatoshiAmount.coerceAtLeast(0L))
binding.textSendAmount.text = "\$$roundedAmount" binding.textSendAmount.text = "\$$roundedAmount"
// apply address // apply address
binding.inputZcashAddress.setText(model.toAddress) binding.inputZcashAddress.setText(model.toAddress)
@ -117,7 +118,7 @@ class SendFragment : BaseFragment<FragmentSendBinding>(),
private fun onMemoUpdated() { private fun onMemoUpdated() {
val totalLength = sendViewModel.createMemoToSend().length val totalLength = sendViewModel.createMemoToSend().length
binding.textLayoutMemo.helperText = "$totalLength/${ZcashSdk.MAX_MEMO_SIZE} chars" binding.textLayoutMemo.helperText = "$totalLength/${ZcashSdk.MAX_MEMO_SIZE} ${getString(R.string.send_memo_chars_abbreviation)}"
val color = if (totalLength > ZcashSdk.MAX_MEMO_SIZE) R.color.zcashRed else R.color.text_light_dimmed val color = if (totalLength > ZcashSdk.MAX_MEMO_SIZE) R.color.zcashRed else R.color.text_light_dimmed
binding.textLayoutMemo.setHelperTextColor(ColorStateList.valueOf(color.toAppColor())) binding.textLayoutMemo.setHelperTextColor(ColorStateList.valueOf(color.toAppColor()))
} }
@ -135,17 +136,17 @@ class SendFragment : BaseFragment<FragmentSendBinding>(),
} }
private fun onAddressChanged(address: String) { private fun onAddressChanged(address: String) {
resumedScope.launch { lifecycleScope.launchWhenResumed {
val validation = sendViewModel.validateAddress(address) val validation = sendViewModel.validateAddress(address)
binding.buttonSend.isActivated = !validation.isNotValid binding.buttonSend.isActivated = !validation.isNotValid
var type = when (validation) { var type = when (validation) {
is AddressType.Transparent -> "This is a valid transparent address" to R.color.zcashGreen is AddressType.Transparent -> R.string.send_validation_address_valid_taddr to R.color.zcashGreen
is AddressType.Shielded -> "This is a valid shielded address" to R.color.zcashGreen is AddressType.Shielded -> R.string.send_validation_address_valid_zaddr to R.color.zcashGreen
is AddressType.Invalid -> "This address appears to be invalid" to R.color.zcashRed is AddressType.Invalid -> R.string.send_validation_address_invalid to R.color.zcashRed
} }
if (address == sendViewModel.synchronizer.getAddress()) type = if (address == sendViewModel.synchronizer.getAddress()) type =
"Warning, this appears to be your address!" to R.color.zcashRed R.string.send_validation_address_self to R.color.zcashRed
binding.textLayoutAddress.helperText = type.first binding.textLayoutAddress.helperText = getString(type.first)
binding.textLayoutAddress.setHelperTextColor(ColorStateList.valueOf(type.second.toAppColor())) binding.textLayoutAddress.setHelperTextColor(ColorStateList.valueOf(type.second.toAppColor()))
// if we have the clipboard address but we're changing it, then clear the selection // if we have the clipboard address but we're changing it, then clear the selection
@ -170,9 +171,9 @@ class SendFragment : BaseFragment<FragmentSendBinding>(),
private fun onSubmit(unused: EditText? = null) { private fun onSubmit(unused: EditText? = null) {
sendViewModel.toAddress = binding.inputZcashAddress.text.toString() sendViewModel.toAddress = binding.inputZcashAddress.text.toString()
sendViewModel.validate(availableZatoshi, maxZatoshi).onFirstWith(resumedScope) { errorMessage -> sendViewModel.validate(requireContext(), availableZatoshi, maxZatoshi).onFirstWith(resumedScope) { errorMessage ->
if (errorMessage == null) { if (errorMessage == null) {
mainActivity?.authenticate("Please confirm that you want to send\n${sendViewModel.zatoshiAmount.convertZatoshiToZecString(8)} ZEC to\n${sendViewModel.toAddress.toAbbreviatedAddress()}") { mainActivity?.authenticate("${getString(R.string.send_confirmation_prompt)}\n${WalletZecFormmatter.toZecStringFull(sendViewModel.zatoshiAmount)} ZEC ${getString(R.string.send_final_to)}\n${sendViewModel.toAddress.toAbbreviatedAddress()}") {
// sendViewModel.funnel(Send.AddressPageComplete) // sendViewModel.funnel(Send.AddressPageComplete)
mainActivity?.safeNavigate(R.id.action_nav_send_to_nav_send_final) mainActivity?.safeNavigate(R.id.action_nav_send_to_nav_send_final)
} }
@ -189,7 +190,7 @@ class SendFragment : BaseFragment<FragmentSendBinding>(),
private fun onMax() { private fun onMax() {
if (maxZatoshi != null) { if (maxZatoshi != null) {
// binding.inputZcashAmount.apply { // binding.inputZcashAmount.apply {
// setText(maxZatoshi.convertZatoshiToZecString(8)) // setText(WalletZecFormmatter.toZecStringFull(maxZatoshi))
// postDelayed({ // postDelayed({
// requestFocus() // requestFocus()
// setSelection(text?.length ?: 0) // setSelection(text?.length ?: 0)
@ -223,7 +224,7 @@ class SendFragment : BaseFragment<FragmentSendBinding>(),
private fun onBalanceUpdated(balance: WalletBalance) { private fun onBalanceUpdated(balance: WalletBalance) {
// binding.textLayoutAmount.helperText = // binding.textLayoutAmount.helperText =
// "You have ${balance.availableZatoshi.coerceAtLeast(0L).convertZatoshiToZecString(8)} available" // "You have ${WalletZecFormmatter.toZecStringFull(balance.availableZatoshi.coerceAtLeast(0L))} available"
maxZatoshi = (balance.availableZatoshi - ZcashSdk.MINERS_FEE_ZATOSHI).coerceAtLeast(0L) maxZatoshi = (balance.availableZatoshi - ZcashSdk.MINERS_FEE_ZATOSHI).coerceAtLeast(0L)
availableZatoshi = balance.availableZatoshi availableZatoshi = balance.availableZatoshi
} }
@ -264,7 +265,7 @@ class SendFragment : BaseFragment<FragmentSendBinding>(),
selected, selected,
address.takeUnless { isBoth }) address.takeUnless { isBoth })
} }
binding.dividerClipboard.text = if (isBoth) "Last Used and On Clipboard" else "On Clipboard" binding.dividerClipboard.setText(if (isBoth) R.string.send_history_last_and_clipboard else R.string.send_history_clipboard)
} }
private fun updateAddressBanner( private fun updateAddressBanner(

View File

@ -1,7 +1,11 @@
package cash.z.ecc.android.ui.send package cash.z.ecc.android.ui.send
import android.content.Context
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import cash.z.ecc.android.R
import cash.z.ecc.android.ext.Const
import cash.z.ecc.android.ext.WalletZecFormmatter
import cash.z.ecc.android.feedback.Feedback import cash.z.ecc.android.feedback.Feedback
import cash.z.ecc.android.feedback.Feedback.Keyed import cash.z.ecc.android.feedback.Feedback.Keyed
import cash.z.ecc.android.feedback.Feedback.TimeMetric import cash.z.ecc.android.feedback.Feedback.TimeMetric
@ -13,6 +17,7 @@ import cash.z.ecc.android.feedback.Report.MetricType
import cash.z.ecc.android.feedback.Report.MetricType.* import cash.z.ecc.android.feedback.Report.MetricType.*
import cash.z.ecc.android.lockbox.LockBox import cash.z.ecc.android.lockbox.LockBox
import cash.z.ecc.android.sdk.Synchronizer import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.annotation.OpenForTesting
import cash.z.ecc.android.sdk.db.entity.* import cash.z.ecc.android.sdk.db.entity.*
import cash.z.ecc.android.sdk.ext.ZcashSdk import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString
@ -31,7 +36,8 @@ import javax.inject.Inject
class SendViewModel @Inject constructor() : ViewModel() { class SendViewModel @Inject constructor() : ViewModel() {
private val metrics = mutableMapOf<String, TimeMetric>() // note used in testing
val metrics = mutableMapOf<String, TimeMetric>()
@Inject @Inject
lateinit var lockBox: LockBox lateinit var lockBox: LockBox
@ -60,7 +66,7 @@ class SendViewModel @Inject constructor() : ViewModel() {
funnel(SendSelected) funnel(SendSelected)
val memoToSend = createMemoToSend() val memoToSend = createMemoToSend()
val keys = DerivationTool.deriveSpendingKeys( val keys = DerivationTool.deriveSpendingKeys(
lockBox.getBytes(WalletSetupViewModel.LockBoxKey.SEED)!! lockBox.getBytes(Const.Backup.SEED)!!
) )
funnel(SpendingKeyFound) funnel(SpendingKeyFound)
reportIssues(memoToSend) reportIssues(memoToSend)
@ -100,29 +106,29 @@ class SendViewModel @Inject constructor() : ViewModel() {
suspend fun validateAddress(address: String): AddressType = suspend fun validateAddress(address: String): AddressType =
synchronizer.validateAddress(address) synchronizer.validateAddress(address)
fun validate(availableZatoshi: Long?, maxZatoshi: Long?) = flow<String?> { fun validate(context: Context, availableZatoshi: Long?, maxZatoshi: Long?) = flow<String?> {
when { when {
synchronizer.validateAddress(toAddress).isNotValid -> { synchronizer.validateAddress(toAddress).isNotValid -> {
emit("Please enter a valid address.") emit(context.getString(R.string.send_validation_error_address_invalid))
} }
zatoshiAmount < 1 -> { zatoshiAmount < 1 -> {
emit("Please go back and enter at least 1 Zatoshi.") emit(context.getString(R.string.send_validation_error_amount_minimum))
} }
availableZatoshi == null -> { availableZatoshi == null -> {
emit("Available funds not found. Please try again in a moment.") emit(context.getString(R.string.send_validation_error_unknown_funds))
} }
availableZatoshi == 0L -> { availableZatoshi == 0L -> {
emit("No funds available to send.") emit(context.getString(R.string.send_validation_error_no_available_funds))
} }
availableZatoshi > 0 && availableZatoshi < ZcashSdk.MINERS_FEE_ZATOSHI -> { availableZatoshi > 0 && availableZatoshi < ZcashSdk.MINERS_FEE_ZATOSHI -> {
emit("Insufficient funds to cover miner's fee.") emit(context.getString(R.string.send_validation_error_dust))
} }
maxZatoshi != null && zatoshiAmount > maxZatoshi -> { maxZatoshi != null && zatoshiAmount > maxZatoshi -> {
emit( "Please go back and enter no more than ${maxZatoshi.convertZatoshiToZecString(8)} ZEC.") emit(context.getString(R.string.send_validation_error_too_much, WalletZecFormmatter.toZecStringFull(maxZatoshi)))
} }
createMemoToSend().length > ZcashSdk.MAX_MEMO_SIZE -> { createMemoToSend().length > ZcashSdk.MAX_MEMO_SIZE -> {
emit( "Memo must be less than ${ZcashSdk.MAX_MEMO_SIZE} in length.") emit(context.getString(R.string.send_validation_error_memo_length, ZcashSdk.MAX_MEMO_SIZE))
} }
else -> emit(null) else -> emit(null)
} }

View File

@ -60,7 +60,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding>() {
private fun onResetClicked(unused: View?) { private fun onResetClicked(unused: View?) {
mainActivity?.hideKeyboard() mainActivity?.hideKeyboard()
context?.showUpdateServerDialog("Restore Defaults") { context?.showUpdateServerDialog(R.string.settings_buttons_restore) {
resumedScope.launch { resumedScope.launch {
binding.groupLoading.visible() binding.groupLoading.visible()
binding.loadingView.requestFocus() binding.loadingView.requestFocus()
@ -117,7 +117,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding>() {
if (uiModel.complete) { if (uiModel.complete) {
binding.groupLoading.gone() binding.groupLoading.gone()
mainActivity?.safeNavigate(R.id.nav_home) mainActivity?.safeNavigate(R.id.nav_home)
Toast.makeText(ZcashWalletApp.instance, "Successfully changed server!", Toast.LENGTH_SHORT).show() Toast.makeText(ZcashWalletApp.instance, getString(R.string.settings_toast_change_server_success), Toast.LENGTH_SHORT).show()
true true
} }
false false
@ -133,7 +133,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding>() {
val message = "An error occured while changing servers. Please verify the info" + val message = "An error occured while changing servers. Please verify the info" +
" and try again.\n\nError: $details" " and try again.\n\nError: $details"
twig(message) twig(message)
Toast.makeText(ZcashWalletApp.instance, "Failed to change server!", Toast.LENGTH_SHORT).show() Toast.makeText(ZcashWalletApp.instance, getString(R.string.settings_toast_change_server_failure), Toast.LENGTH_SHORT).show()
context?.showUpdateServerCriticalError(message) context?.showUpdateServerCriticalError(message)
} }

View File

@ -10,9 +10,11 @@ import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.activity.addCallback import androidx.activity.addCallback
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import cash.z.ecc.android.R
import cash.z.ecc.android.ZcashWalletApp import cash.z.ecc.android.ZcashWalletApp
import cash.z.ecc.android.databinding.FragmentBackupBinding import cash.z.ecc.android.databinding.FragmentBackupBinding
import cash.z.ecc.android.di.viewmodel.activityViewModel import cash.z.ecc.android.di.viewmodel.activityViewModel
import cash.z.ecc.android.ext.Const
import cash.z.ecc.android.feedback.Report import cash.z.ecc.android.feedback.Report
import cash.z.ecc.android.feedback.Report.MetricType.SEED_PHRASE_LOADED import cash.z.ecc.android.feedback.Report.MetricType.SEED_PHRASE_LOADED
import cash.z.ecc.android.feedback.Report.Tap.BACKUP_DONE import cash.z.ecc.android.feedback.Report.Tap.BACKUP_DONE
@ -22,7 +24,6 @@ import cash.z.ecc.android.lockbox.LockBox
import cash.z.ecc.android.sdk.ext.ZcashSdk import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.ext.twig import cash.z.ecc.android.sdk.ext.twig
import cash.z.ecc.android.ui.base.BaseFragment import cash.z.ecc.android.ui.base.BaseFragment
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.LockBoxKey
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.SEED_WITH_BACKUP import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.SEED_WITH_BACKUP
import cash.z.ecc.android.ui.util.AddressPartNumberSpan import cash.z.ecc.android.ui.util.AddressPartNumberSpan
import cash.z.ecc.kotlin.mnemonic.Mnemonics import cash.z.ecc.kotlin.mnemonic.Mnemonics
@ -36,7 +37,7 @@ import kotlinx.coroutines.withContext
class BackupFragment : BaseFragment<FragmentBackupBinding>() { class BackupFragment : BaseFragment<FragmentBackupBinding>() {
override val screen = Report.Screen.BACKUP override val screen = Report.Screen.BACKUP
val walletSetup: WalletSetupViewModel by activityViewModel(false) private val walletSetup: WalletSetupViewModel by activityViewModel(false)
private var hasBackUp: Boolean = true //TODO: implement backup and then check for it here-ish private var hasBackUp: Boolean = true //TODO: implement backup and then check for it here-ish
@ -61,7 +62,7 @@ class BackupFragment : BaseFragment<FragmentBackupBinding>() {
onEnterWallet().also { if (hasBackUp) tapped(BACKUP_DONE) else tapped(BACKUP_VERIFY) } onEnterWallet().also { if (hasBackUp) tapped(BACKUP_DONE) else tapped(BACKUP_VERIFY) }
} }
if (hasBackUp) { if (hasBackUp) {
binding.buttonPositive.text = "Done" binding.buttonPositive.text = getString(R.string.backup_button_done)
} }
} }
@ -74,10 +75,9 @@ class BackupFragment : BaseFragment<FragmentBackupBinding>() {
override fun onAttach(context: Context) { override fun onAttach(context: Context) {
super.onAttach(context) super.onAttach(context)
walletSetup.checkSeed().onEach { walletSetup.checkSeed().onEach {
when(it) { hasBackUp = when(it) {
SEED_WITH_BACKUP -> { SEED_WITH_BACKUP -> true
hasBackUp = true else -> false
}
} }
}.launchIn(lifecycleScope) }.launchIn(lifecycleScope)
} }
@ -85,14 +85,14 @@ class BackupFragment : BaseFragment<FragmentBackupBinding>() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
resumedScope.launch { resumedScope.launch {
binding.textBirtdate.text = "Birthday Height: %,d".format(calculateBirthday()) binding.textBirtdate.text = getString(R.string.backup_format_birthday_height, calculateBirthday())
} }
} }
// TODO: move this into the SDK // TODO: move this into the SDK
private suspend fun calculateBirthday(): Int { private suspend fun calculateBirthday(): Int {
var storedBirthday: Int = 0 var storedBirthday = 0
var oldestTransactionHeight:Int = 0 var oldestTransactionHeight = 0
try { try {
storedBirthday = walletSetup.loadBirthdayHeight() ?: 0 storedBirthday = walletSetup.loadBirthdayHeight() ?: 0
oldestTransactionHeight = mainActivity?.synchronizerComponent?.synchronizer()?.receivedTransactions?.first()?.last()?.minedHeight ?: 0 oldestTransactionHeight = mainActivity?.synchronizerComponent?.synchronizer()?.receivedTransactions?.first()?.last()?.minedHeight ?: 0
@ -109,7 +109,7 @@ class BackupFragment : BaseFragment<FragmentBackupBinding>() {
private fun onEnterWallet(showMessage: Boolean = !this.hasBackUp) { private fun onEnterWallet(showMessage: Boolean = !this.hasBackUp) {
if (showMessage) { if (showMessage) {
Toast.makeText(activity, "Backup verification coming soon!", Toast.LENGTH_LONG).show() Toast.makeText(activity, R.string.backup_verification_not_implemented, Toast.LENGTH_LONG).show()
} }
mainActivity?.navController?.popBackStack() mainActivity?.navController?.popBackStack()
} }
@ -131,7 +131,7 @@ class BackupFragment : BaseFragment<FragmentBackupBinding>() {
mainActivity!!.feedback.measure(SEED_PHRASE_LOADED) { mainActivity!!.feedback.measure(SEED_PHRASE_LOADED) {
val lockBox = LockBox(ZcashWalletApp.instance) val lockBox = LockBox(ZcashWalletApp.instance)
val mnemonics = Mnemonics() val mnemonics = Mnemonics()
val seedPhrase = lockBox.getCharsUtf8(LockBoxKey.SEED_PHRASE)!! val seedPhrase = lockBox.getCharsUtf8(Const.Backup.SEED_PHRASE)!!
val result = mnemonics.toWordList(seedPhrase) val result = mnemonics.toWordList(seedPhrase)
result result
} }

View File

@ -4,10 +4,13 @@ import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.widget.Toast
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import cash.z.ecc.android.R import cash.z.ecc.android.R
import cash.z.ecc.android.databinding.FragmentLandingBinding import cash.z.ecc.android.databinding.FragmentLandingBinding
import cash.z.ecc.android.di.viewmodel.activityViewModel import cash.z.ecc.android.di.viewmodel.activityViewModel
import cash.z.ecc.android.ext.locale
import cash.z.ecc.android.ext.toAppString
import cash.z.ecc.android.feedback.Report import cash.z.ecc.android.feedback.Report
import cash.z.ecc.android.feedback.Report.Funnel.Restore import cash.z.ecc.android.feedback.Report.Funnel.Restore
import cash.z.ecc.android.feedback.Report.Tap.* import cash.z.ecc.android.feedback.Report.Tap.*
@ -18,6 +21,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.lang.IllegalStateException
class LandingFragment : BaseFragment<FragmentLandingBinding>() { class LandingFragment : BaseFragment<FragmentLandingBinding>() {
override val screen = Report.Screen.LANDING override val screen = Report.Screen.LANDING
@ -32,14 +36,14 @@ class LandingFragment : BaseFragment<FragmentLandingBinding>() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
binding.buttonPositive.setOnClickListener { binding.buttonPositive.setOnClickListener {
when (binding.buttonPositive.text.toString().toLowerCase()) { when (binding.buttonPositive.text.toString().toLowerCase(locale())) {
"new" -> onNewWallet().also { tapped(LANDING_NEW) } R.string.landing_button_primary.toAppString(true) -> onNewWallet().also { tapped(LANDING_NEW) }
"backup" -> onBackupWallet().also { tapped(LANDING_BACKUP) } R.string.landing_button_primary_create_success.toAppString(true) -> onBackupWallet().also { tapped(LANDING_BACKUP) }
} }
} }
binding.buttonNegative.setOnLongClickListener { binding.buttonNegative.setOnLongClickListener {
tapped(DEVELOPER_WALLET_PROMPT) tapped(DEVELOPER_WALLET_PROMPT)
if (binding.buttonNegative.text.toString().toLowerCase() == "restore") { if (binding.buttonNegative.text.toString().toLowerCase(locale()) == "restore") {
MaterialAlertDialogBuilder(activity) MaterialAlertDialogBuilder(activity)
.setMessage("Would you like to import the dev wallet?\n\nIf so, please only send 0.0001 ZEC at a time and return some later so that the account remains funded.") .setMessage("Would you like to import the dev wallet?\n\nIf so, please only send 0.0001 ZEC at a time and return some later so that the account remains funded.")
.setTitle("Import Dev Wallet?") .setTitle("Import Dev Wallet?")
@ -55,12 +59,13 @@ class LandingFragment : BaseFragment<FragmentLandingBinding>() {
} }
.show() .show()
true true
} } else {
false false
} }
}
binding.buttonNegative.setOnClickListener { binding.buttonNegative.setOnClickListener {
when (binding.buttonNegative.text.toString().toLowerCase()) { when (binding.buttonNegative.text.toString().toLowerCase(locale())) {
"restore" -> onRestoreWallet().also { R.string.landing_button_secondary.toAppString(true) -> onRestoreWallet().also {
mainActivity?.reportFunnel(Restore.Initiated) mainActivity?.reportFunnel(Restore.Initiated)
tapped(LANDING_RESTORE) tapped(LANDING_RESTORE)
} }
@ -76,6 +81,7 @@ class LandingFragment : BaseFragment<FragmentLandingBinding>() {
SEED_WITHOUT_BACKUP, SEED_WITH_BACKUP -> { SEED_WITHOUT_BACKUP, SEED_WITH_BACKUP -> {
mainActivity?.safeNavigate(R.id.nav_backup) mainActivity?.safeNavigate(R.id.nav_backup)
} }
else -> {}
} }
}.launchIn(lifecycleScope) }.launchIn(lifecycleScope)
} }
@ -89,15 +95,13 @@ class LandingFragment : BaseFragment<FragmentLandingBinding>() {
when (count) { when (count) {
1 -> { 1 -> {
tapped(LANDING_BACKUP_SKIPPED_1) tapped(LANDING_BACKUP_SKIPPED_1)
binding.textMessage.text = binding.textMessage.setText(R.string.landing_backup_skipped_message_1)
"Are you sure? Without a backup, funds can be lost FOREVER!" binding.buttonNegative.setText(R.string.landing_button_backup_skipped_1)
binding.buttonNegative.text = "Later"
} }
2 -> { 2 -> {
tapped(LANDING_BACKUP_SKIPPED_2) tapped(LANDING_BACKUP_SKIPPED_2)
binding.textMessage.text = binding.textMessage.setText(R.string.landing_backup_skipped_message_2)
"You can't backup later. You're probably going to lose your funds!" binding.buttonNegative.setText(R.string.landing_button_backup_skipped_2)
binding.buttonNegative.text = "I've been warned"
} }
else -> { else -> {
tapped(LANDING_BACKUP_SKIPPED_3) tapped(LANDING_BACKUP_SKIPPED_3)
@ -113,15 +117,15 @@ class LandingFragment : BaseFragment<FragmentLandingBinding>() {
// AKA import wallet // AKA import wallet
private fun onUseDevWallet() { private fun onUseDevWallet() {
val seedPhrase = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread" val seedPhrase = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread"
val birthday = 663174//626599 val birthday = 663174 //991645
mainActivity?.apply { mainActivity?.apply {
lifecycleScope.launch { lifecycleScope.launch {
mainActivity?.startSync(walletSetup.importWallet(seedPhrase, birthday)) mainActivity?.startSync(walletSetup.importWallet(seedPhrase, birthday))
} }
binding.buttonPositive.isEnabled = true binding.buttonPositive.isEnabled = true
binding.textMessage.text = "Wallet imported! Congratulations!" binding.textMessage.setText(R.string.landing_import_success_message)
binding.buttonNegative.text = "Skip" binding.buttonNegative.setText(R.string.landing_button_secondary_import_success)
binding.buttonPositive.text = "Backup" binding.buttonPositive.setText(R.string.landing_import_success_primary_button)
playSound("sound_receive_small.mp3") playSound("sound_receive_small.mp3")
vibrateSuccess() vibrateSuccess()
} }
@ -129,18 +133,27 @@ class LandingFragment : BaseFragment<FragmentLandingBinding>() {
private fun onNewWallet() { private fun onNewWallet() {
lifecycleScope.launch { lifecycleScope.launch {
val ogText = binding.buttonPositive.text binding.buttonPositive.setText(R.string.landing_button_progress_create)
binding.buttonPositive.text = "creating"
binding.buttonPositive.isEnabled = false binding.buttonPositive.isEnabled = false
mainActivity?.startSync(walletSetup.newWallet()) try {
val initializer = walletSetup.newWallet()
if (!initializer.accountsCreated) {
binding.buttonPositive.isEnabled = true
binding.buttonPositive.setText(R.string.landing_button_primary)
throw IllegalStateException("New wallet should result in accounts table being created")
}
mainActivity?.startSync(initializer)
binding.buttonPositive.isEnabled = true binding.buttonPositive.isEnabled = true
binding.textMessage.text = "Wallet created! Congratulations!" binding.textMessage.setText(R.string.landing_create_success_message)
binding.buttonNegative.text = "Skip" binding.buttonNegative.setText(R.string.landing_button_secondary_create_success)
binding.buttonPositive.text = "Backup" binding.buttonPositive.setText(R.string.landing_button_primary_create_success)
mainActivity?.playSound("sound_receive_small.mp3") mainActivity?.playSound("sound_receive_small.mp3")
mainActivity?.vibrateSuccess() mainActivity?.vibrateSuccess()
} catch (t: Throwable) {
Toast.makeText(context, "Failed to create wallet", Toast.LENGTH_SHORT).show()
}
} }
} }

View File

@ -1,24 +1,20 @@
package cash.z.ecc.android.ui.setup package cash.z.ecc.android.ui.setup
import android.util.Log
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import cash.z.ecc.android.ZcashWalletApp import cash.z.ecc.android.ZcashWalletApp
import cash.z.ecc.android.ext.Const import cash.z.ecc.android.ext.Const
import cash.z.ecc.android.feedback.Feedback import cash.z.ecc.android.feedback.Feedback
import cash.z.ecc.android.feedback.Report.MetricType.*
import cash.z.ecc.android.feedback.measure
import cash.z.ecc.android.lockbox.LockBox import cash.z.ecc.android.lockbox.LockBox
import cash.z.ecc.android.sdk.Initializer import cash.z.ecc.android.sdk.Initializer
import cash.z.ecc.android.sdk.exception.InitializerException import cash.z.ecc.android.sdk.exception.InitializerException
import cash.z.ecc.android.sdk.ext.toHex import cash.z.ecc.android.sdk.ext.twig
import cash.z.ecc.android.sdk.tool.DerivationTool import cash.z.ecc.android.sdk.tool.DerivationTool
import cash.z.ecc.android.sdk.tool.WalletBirthdayTool import cash.z.ecc.android.sdk.tool.WalletBirthdayTool
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.* import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.*
import cash.z.ecc.kotlin.mnemonic.Mnemonics import cash.z.ecc.kotlin.mnemonic.Mnemonics
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.single
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Named import javax.inject.Named
@ -39,152 +35,17 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() {
lateinit var feedback: Feedback lateinit var feedback: Feedback
enum class WalletSetupState { enum class WalletSetupState {
UNKNOWN, SEED_WITH_BACKUP, SEED_WITHOUT_BACKUP, NO_SEED SEED_WITH_BACKUP, SEED_WITHOUT_BACKUP, NO_SEED
} }
fun checkSeed(): Flow<WalletSetupState> = flow { fun checkSeed(): Flow<WalletSetupState> = flow {
when { when {
lockBox.getBoolean(LockBoxKey.HAS_BACKUP) -> emit(SEED_WITH_BACKUP) lockBox.getBoolean(Const.Backup.HAS_BACKUP) -> emit(SEED_WITH_BACKUP)
lockBox.getBoolean(LockBoxKey.HAS_SEED) -> emit(SEED_WITHOUT_BACKUP) lockBox.getBoolean(Const.Backup.HAS_SEED) -> emit(SEED_WITHOUT_BACKUP)
else -> emit(NO_SEED) else -> emit(NO_SEED)
} }
} }
suspend fun buildInitializer() = ZcashWalletApp.component.initializerSubcomponent().create(loadConfig().single()).initializer()
private fun loadConfig() = flow<Initializer.Builder> {
emit(
Initializer.Builder { builder ->
val vk = lockBox.getCharsUtf8(LockBoxKey.VIEWING_KEY)?.let { String(it) }
?: throw InitializerException.MissingViewingKeyException
val birthdayHeight = loadBirthdayHeight()
?: throw InitializerException.MissingBirthdayException
val host = prefs[Const.Pref.SERVER_HOST] ?: Const.Default.Server.HOST
val port = prefs[Const.Pref.SERVER_PORT] ?: Const.Default.Server.PORT
builder.import(vk, birthdayHeight, host, port)
}
)
}
fun loadBirthdayHeight(): Int? {
val h: Int? = lockBox[LockBoxKey.BIRTHDAY_HEIGHT]
twigFix("Loaded birthday with key ${LockBoxKey.BIRTHDAY_HEIGHT} and found $h")
return h
}
// /**
// * Re-open an existing wallet. This is the most common use case, where a user has previously
// * created or imported their seed and is returning to the wallet. In other words, this is the
// * non-FTUE case.
// */
// fun openWallet(): Initializer {
// twigFix("Opening existing wallet")
// return ZcashWalletApp.component.initializerSubcomponent()
// .create(DefaultBirthdayStore(ZcashWalletApp.instance)).run {
// initializer().open(birthdayStore().getBirthday())
// }
// }
suspend fun newWallet(): Initializer {
twigFix("Initializing new wallet")
createWallet()
return buildInitializer()
}
suspend fun importWallet(seedPhrase: String, birthdayHeight: Int): Initializer {
twigFix("Importing wallet. Requested birthday: $birthdayHeight")
val seedPhraseChars = seedPhrase.toCharArray()
storeSeedPhrase(seedPhraseChars)
storeSeed(mnemonics.toSeed(seedPhraseChars))
WalletBirthdayTool.loadNearest(ZcashWalletApp.instance, birthdayHeight).let { birthday ->
storeBirthday(birthday)
}
return buildInitializer()
}
private fun twigFix(s: String) {
Log.e("@TWIG", s)
}
/**
* Take all the steps necessary to create a new wallet and measure how long it takes.
*
* @param feedback the object used for measurement.
*/
private suspend fun createWallet(): ByteArray = withContext(Dispatchers.IO) {
check(!lockBox.getBoolean(LockBoxKey.HAS_SEED)) {
"Error! Cannot create a seed when one already exists! This would overwrite the" +
" existing seed and could lead to a loss of funds if the user has no backup!"
}
feedback.measure(WALLET_CREATED) {
mnemonics.run {
feedback.measure(ENTROPY_CREATED) { nextEntropy() }.let { entropy ->
feedback.measure(SEED_PHRASE_CREATED) { nextMnemonic(entropy) }
.let { seedPhrase ->
feedback.measure(SEED_CREATED) { toSeed(seedPhrase) }.let { bip39Seed ->
storeSeedPhrase(seedPhrase)
storeSeed(bip39Seed)
WalletBirthdayTool.loadNearest(ZcashWalletApp.instance).let { birthday ->
storeBirthday(birthday)
}
bip39Seed
}
}
}
}
}
}
private fun storeBirthday(birthday: WalletBirthdayTool.WalletBirthday) {
twigFix("Storing birthday ${birthday.height} with and key ${LockBoxKey.BIRTHDAY_HEIGHT}")
lockBox[LockBoxKey.BIRTHDAY_HEIGHT] = birthday.height
}
private fun storeSeed(bip39Seed: ByteArray) {
twigFix("Storing seed: ${bip39Seed.toHex().length}")
lockBox.setBytes(LockBoxKey.SEED, bip39Seed)
lockBox[LockBoxKey.VIEWING_KEY] = DerivationTool.deriveViewingKeys(bip39Seed)[0]
lockBox[LockBoxKey.HAS_SEED] = true
}
private fun storeSeedPhrase(seedPhrase: CharArray) {
twigFix("Storing seedphrase: ${seedPhrase.size}")
lockBox[LockBoxKey.SEED_PHRASE] = seedPhrase
lockBox[LockBoxKey.HAS_SEED_PHRASE] = true
}
/**
* Take all the steps necessary to import a wallet and measure how long it takes.
*
* @param feedback the object used for measurement.
*/
private suspend fun importWallet(
seedPhrase: CharArray
): ByteArray = withContext(Dispatchers.IO) {
check(!lockBox.getBoolean(LockBoxKey.HAS_SEED)) {
"Error! Cannot import a seed when one already exists! This would overwrite the" +
" existing seed and could lead to a loss of funds if the user has no backup!"
}
feedback.measure(WALLET_IMPORTED) {
mnemonics.run {
feedback.measure(SEED_IMPORTED) { toSeed(seedPhrase) }.let { bip39Seed ->
storeSeedPhrase(seedPhrase)
lockBox.setBytes(LockBoxKey.SEED, bip39Seed)
lockBox[LockBoxKey.HAS_SEED] = true
bip39Seed
}
}
}
}
/** /**
* Throw an exception if the seed phrase is bad. * Throw an exception if the seed phrase is bad.
*/ */
@ -192,15 +53,94 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() {
mnemonics.validate(seedPhrase.toCharArray()) mnemonics.validate(seedPhrase.toCharArray())
} }
object LockBoxKey { fun loadBirthdayHeight(): Int? {
const val SEED = "cash.z.ecc.android.SEED" val h: Int? = lockBox[Const.Backup.BIRTHDAY_HEIGHT]
const val SEED_PHRASE = "cash.z.ecc.android.SEED_PHRASE" twig("Loaded birthday with key ${Const.Backup.BIRTHDAY_HEIGHT} and found $h")
const val HAS_SEED = "cash.z.ecc.android.HAS_SEED" return h
const val HAS_SEED_PHRASE = "cash.z.ecc.android.HAS_SEED_PHRASE" }
const val HAS_BACKUP = "cash.z.ecc.android.HAS_BACKUP"
// Config suspend fun newWallet(): Initializer {
const val VIEWING_KEY = "cash.z.ecc.android.VIEWING_KEY" twig("Initializing new wallet")
const val BIRTHDAY_HEIGHT = "cash.z.ecc.android.BIRTHDAY_HEIGHT" with(mnemonics) {
storeWallet(nextMnemonic(nextEntropy()), loadNearestBirthday())
}
return openStoredWallet()
}
suspend fun importWallet(seedPhrase: String, birthdayHeight: Int): Initializer {
twig("Importing wallet. Requested birthday: $birthdayHeight")
storeWallet(seedPhrase.toCharArray(), loadNearestBirthday(birthdayHeight))
return openStoredWallet()
}
suspend fun openStoredWallet(): Initializer {
val config = loadConfig()
return ZcashWalletApp.component.initializerSubcomponent().create(config).initializer()
}
private fun loadConfig() = Initializer.Builder { builder ->
val vk = lockBox.getCharsUtf8(Const.Backup.VIEWING_KEY)?.let { String(it) }
?: throw InitializerException.MissingViewingKeyException
val birthdayHeight = loadBirthdayHeight()
?: throw InitializerException.MissingBirthdayException
val host = prefs[Const.Pref.SERVER_HOST] ?: Const.Default.Server.HOST
val port = prefs[Const.Pref.SERVER_PORT] ?: Const.Default.Server.PORT
builder.import(vk, birthdayHeight, host, port)
}
private fun loadNearestBirthday(birthdayHeight: Int? = null) =
WalletBirthdayTool.loadNearest(ZcashWalletApp.instance, birthdayHeight)
//
// Storage Helpers
//
/**
* Entry point for all storage. Takes a seed phrase and stores all the parts so that we can
* selectively use them, the next time the app is opened. Although we store everything, we
* primarily only work with the viewing key and spending key. The seed is only accessed when
* presenting backup information to the user.
*/
private suspend fun storeWallet(seedPhraseChars: CharArray, birthday: WalletBirthdayTool.WalletBirthday) {
check(!lockBox.getBoolean(Const.Backup.HAS_SEED)) {
"Error! Cannot store a seed when one already exists! This would overwrite the" +
" existing seed and could lead to a loss of funds if the user has no backup!"
}
storeBirthday(birthday)
mnemonics.toSeed(seedPhraseChars).let { bip39Seed ->
DerivationTool.deriveViewingKeys(bip39Seed)[0].let { viewingKey ->
storeSeedPhrase(seedPhraseChars)
storeSeed(bip39Seed)
storeViewingKey(viewingKey)
} }
} }
}
private suspend fun storeBirthday(birthday: WalletBirthdayTool.WalletBirthday) = withContext(IO) {
twig("Storing birthday ${birthday.height} with and key ${Const.Backup.BIRTHDAY_HEIGHT}")
lockBox[Const.Backup.BIRTHDAY_HEIGHT] = birthday.height
}
private suspend fun storeSeedPhrase(seedPhrase: CharArray) = withContext(IO) {
twig("Storing seedphrase: ${seedPhrase.size}")
lockBox[Const.Backup.SEED_PHRASE] = seedPhrase
lockBox[Const.Backup.HAS_SEED_PHRASE] = true
}
private suspend fun storeSeed(bip39Seed: ByteArray) = withContext(IO) {
twig("Storing seed: ${bip39Seed.size}")
lockBox.setBytes(Const.Backup.SEED, bip39Seed)
lockBox[Const.Backup.HAS_SEED] = true
}
private suspend fun storeViewingKey(vk: String) = withContext(IO) {
twig("storeViewingKey vk: ${vk.length}")
lockBox[Const.Backup.VIEWING_KEY] = vk
}
}

View File

@ -0,0 +1,21 @@
package cash.z.ecc.android.ui.util
import cash.z.ecc.android.ZcashWalletApp
import cash.z.ecc.android.sdk.ext.TroubleshootingTwig
import okio.Okio
import java.io.File
class DebugFileTwig(fileName: String = "developer_log.txt") : TroubleshootingTwig() {
val file = File("${ZcashWalletApp.instance.filesDir}/logs", fileName)
override fun twig(logMessage: String) {
super.twig(logMessage)
appendToFile(formatter(logMessage))
}
private fun appendToFile(message: String) {
Okio.buffer(Okio.appendingSink(file)).use {
it.writeUtf8("$message\n")
}
}
}

View File

@ -15,8 +15,8 @@
android:centerX="54" android:centerX="54"
android:centerY="36.01165" android:centerY="36.01165"
android:type="radial"> android:type="radial">
<item android:offset="0" android:color="#FF3F3F4F"/> <item android:offset="0" android:color="@color/app_icon_background_0"/>
<item android:offset="1" android:color="#FF000000"/> <item android:offset="1" android:color="@color/app_icon_background_1"/>
</gradient> </gradient>
</aapt:attr> </aapt:attr>
</path> </path>

View File

@ -45,7 +45,7 @@
<path <path
android:pathData="M77.8,53.601C77.8,53.79 77.799,53.963 77.799,53.963L46.564,53.967C46.755,57.942 50.012,61.117 53.999,61.117C56.367,61.117 58.475,59.995 59.842,58.255L60.739,58.255C59.264,60.419 56.794,61.843 53.999,61.843C49.752,61.843 46.262,58.557 45.872,54.375L45.862,54.261C45.855,54.164 45.855,54.065 45.851,53.966L39.9,53.967C39.769,54.519 39.351,54.953 38.814,55.109C39.569,62.892 46.087,68.996 54,68.996C60.81,68.996 66.592,64.475 68.553,58.254L69.305,58.254C67.32,64.881 61.211,69.721 54,69.721C45.704,69.721 38.873,63.313 38.097,55.149C37.49,55.035 37.009,54.569 36.866,53.967L32.082,53.968C31.949,54.527 31.522,54.965 30.974,55.116C31.752,67.246 41.779,76.875 54,76.875C65.147,76.875 74.471,68.865 76.615,58.254L77.346,58.254C75.19,69.267 65.545,77.6 54,77.6C41.392,77.6 31.048,67.661 30.254,55.146C29.538,55.001 29,54.364 29,53.6C29,52.836 29.539,52.199 30.254,52.054C31.048,39.539 41.391,29.6 54,29.6C67.003,29.601 77.597,40.172 77.791,53.239C77.793,53.36 77.8,53.48 77.8,53.601ZM77.072,53.239C76.878,40.572 66.606,30.327 53.999,30.327C41.778,30.327 31.75,39.957 30.973,52.085C31.522,52.236 31.95,52.677 32.082,53.239L36.863,53.239C37.005,52.635 37.487,52.167 38.095,52.053C38.871,43.888 45.702,37.48 53.999,37.48C61.069,37.48 67.078,42.134 69.182,48.561L68.422,48.561C66.347,42.541 60.667,38.206 53.999,38.206C46.086,38.206 39.568,44.31 38.813,52.092C39.351,52.25 39.77,52.685 39.9,53.239L45.845,53.239C45.852,53.091 45.86,52.961 45.86,52.961C46.186,48.716 49.706,45.36 53.999,45.36C56.623,45.36 58.955,46.617 60.451,48.561L59.513,48.561C58.148,47.045 56.185,46.085 53.999,46.085C50.01,46.085 46.752,49.263 46.562,53.239L46.562,53.239L77.072,53.239Z" android:pathData="M77.8,53.601C77.8,53.79 77.799,53.963 77.799,53.963L46.564,53.967C46.755,57.942 50.012,61.117 53.999,61.117C56.367,61.117 58.475,59.995 59.842,58.255L60.739,58.255C59.264,60.419 56.794,61.843 53.999,61.843C49.752,61.843 46.262,58.557 45.872,54.375L45.862,54.261C45.855,54.164 45.855,54.065 45.851,53.966L39.9,53.967C39.769,54.519 39.351,54.953 38.814,55.109C39.569,62.892 46.087,68.996 54,68.996C60.81,68.996 66.592,64.475 68.553,58.254L69.305,58.254C67.32,64.881 61.211,69.721 54,69.721C45.704,69.721 38.873,63.313 38.097,55.149C37.49,55.035 37.009,54.569 36.866,53.967L32.082,53.968C31.949,54.527 31.522,54.965 30.974,55.116C31.752,67.246 41.779,76.875 54,76.875C65.147,76.875 74.471,68.865 76.615,58.254L77.346,58.254C75.19,69.267 65.545,77.6 54,77.6C41.392,77.6 31.048,67.661 30.254,55.146C29.538,55.001 29,54.364 29,53.6C29,52.836 29.539,52.199 30.254,52.054C31.048,39.539 41.391,29.6 54,29.6C67.003,29.601 77.597,40.172 77.791,53.239C77.793,53.36 77.8,53.48 77.8,53.601ZM77.072,53.239C76.878,40.572 66.606,30.327 53.999,30.327C41.778,30.327 31.75,39.957 30.973,52.085C31.522,52.236 31.95,52.677 32.082,53.239L36.863,53.239C37.005,52.635 37.487,52.167 38.095,52.053C38.871,43.888 45.702,37.48 53.999,37.48C61.069,37.48 67.078,42.134 69.182,48.561L68.422,48.561C66.347,42.541 60.667,38.206 53.999,38.206C46.086,38.206 39.568,44.31 38.813,52.092C39.351,52.25 39.77,52.685 39.9,53.239L45.845,53.239C45.852,53.091 45.86,52.961 45.86,52.961C46.186,48.716 49.706,45.36 53.999,45.36C56.623,45.36 58.955,46.617 60.451,48.561L59.513,48.561C58.148,47.045 56.185,46.085 53.999,46.085C50.01,46.085 46.752,49.263 46.562,53.239L46.562,53.239L77.072,53.239Z"
android:strokeWidth="1" android:strokeWidth="1"
android:fillColor="#FFB900" android:fillColor="@color/app_icon_foreground"
android:fillType="nonZero" android:fillType="nonZero"
android:strokeColor="#00000000"/> android:strokeColor="#00000000"/>
<path <path
@ -90,13 +90,13 @@
<path <path
android:pathData="M77.8,53.601C77.8,53.79 77.799,53.963 77.799,53.963L46.564,53.967C46.755,57.942 50.012,61.117 53.999,61.117C56.367,61.117 58.475,59.995 59.842,58.255L60.739,58.255C59.264,60.419 56.794,61.843 53.999,61.843C49.752,61.843 46.262,58.557 45.872,54.375L45.862,54.261C45.855,54.164 45.855,54.065 45.851,53.966L39.9,53.967C39.769,54.519 39.351,54.953 38.814,55.109C39.569,62.892 46.087,68.996 54,68.996C60.81,68.996 66.592,64.475 68.553,58.254L69.305,58.254C67.32,64.881 61.211,69.721 54,69.721C45.704,69.721 38.873,63.313 38.097,55.149C37.49,55.035 37.009,54.569 36.866,53.967L32.082,53.968C31.949,54.527 31.522,54.965 30.974,55.116C31.752,67.246 41.779,76.875 54,76.875C65.147,76.875 74.471,68.865 76.615,58.254L77.346,58.254C75.19,69.267 65.545,77.6 54,77.6C41.392,77.6 31.048,67.661 30.254,55.146C29.538,55.001 29,54.364 29,53.6C29,52.836 29.539,52.199 30.254,52.054C31.048,39.539 41.391,29.6 54,29.6C67.003,29.601 77.597,40.172 77.791,53.239C77.793,53.36 77.8,53.48 77.8,53.601ZM77.072,53.239C76.878,40.572 66.606,30.327 53.999,30.327C41.778,30.327 31.75,39.957 30.973,52.085C31.522,52.236 31.95,52.677 32.082,53.239L36.863,53.239C37.005,52.635 37.487,52.167 38.095,52.053C38.871,43.888 45.702,37.48 53.999,37.48C61.069,37.48 67.078,42.134 69.182,48.561L68.422,48.561C66.347,42.541 60.667,38.206 53.999,38.206C46.086,38.206 39.568,44.31 38.813,52.092C39.351,52.25 39.77,52.685 39.9,53.239L45.845,53.239C45.852,53.091 45.86,52.961 45.86,52.961C46.186,48.716 49.706,45.36 53.999,45.36C56.623,45.36 58.955,46.617 60.451,48.561L59.513,48.561C58.148,47.045 56.185,46.085 53.999,46.085C50.01,46.085 46.752,49.263 46.562,53.239L46.562,53.239L77.072,53.239Z" android:pathData="M77.8,53.601C77.8,53.79 77.799,53.963 77.799,53.963L46.564,53.967C46.755,57.942 50.012,61.117 53.999,61.117C56.367,61.117 58.475,59.995 59.842,58.255L60.739,58.255C59.264,60.419 56.794,61.843 53.999,61.843C49.752,61.843 46.262,58.557 45.872,54.375L45.862,54.261C45.855,54.164 45.855,54.065 45.851,53.966L39.9,53.967C39.769,54.519 39.351,54.953 38.814,55.109C39.569,62.892 46.087,68.996 54,68.996C60.81,68.996 66.592,64.475 68.553,58.254L69.305,58.254C67.32,64.881 61.211,69.721 54,69.721C45.704,69.721 38.873,63.313 38.097,55.149C37.49,55.035 37.009,54.569 36.866,53.967L32.082,53.968C31.949,54.527 31.522,54.965 30.974,55.116C31.752,67.246 41.779,76.875 54,76.875C65.147,76.875 74.471,68.865 76.615,58.254L77.346,58.254C75.19,69.267 65.545,77.6 54,77.6C41.392,77.6 31.048,67.661 30.254,55.146C29.538,55.001 29,54.364 29,53.6C29,52.836 29.539,52.199 30.254,52.054C31.048,39.539 41.391,29.6 54,29.6C67.003,29.601 77.597,40.172 77.791,53.239C77.793,53.36 77.8,53.48 77.8,53.601ZM77.072,53.239C76.878,40.572 66.606,30.327 53.999,30.327C41.778,30.327 31.75,39.957 30.973,52.085C31.522,52.236 31.95,52.677 32.082,53.239L36.863,53.239C37.005,52.635 37.487,52.167 38.095,52.053C38.871,43.888 45.702,37.48 53.999,37.48C61.069,37.48 67.078,42.134 69.182,48.561L68.422,48.561C66.347,42.541 60.667,38.206 53.999,38.206C46.086,38.206 39.568,44.31 38.813,52.092C39.351,52.25 39.77,52.685 39.9,53.239L45.845,53.239C45.852,53.091 45.86,52.961 45.86,52.961C46.186,48.716 49.706,45.36 53.999,45.36C56.623,45.36 58.955,46.617 60.451,48.561L59.513,48.561C58.148,47.045 56.185,46.085 53.999,46.085C50.01,46.085 46.752,49.263 46.562,53.239L46.562,53.239L77.072,53.239Z"
android:strokeWidth="1" android:strokeWidth="1"
android:fillColor="#FFB900" android:fillColor="@color/app_icon_foreground"
android:fillType="nonZero" android:fillType="nonZero"
android:strokeColor="#00000000"/> android:strokeColor="#00000000"/>
<path <path
android:pathData="M77.8,53.601C77.8,53.79 77.799,53.963 77.799,53.963L46.564,53.967C46.755,57.942 50.012,61.117 53.999,61.117C56.367,61.117 58.475,59.995 59.842,58.255L60.739,58.255C59.264,60.419 56.794,61.843 53.999,61.843C49.752,61.843 46.262,58.557 45.872,54.375L45.862,54.261C45.855,54.164 45.855,54.065 45.851,53.966L39.9,53.967C39.769,54.519 39.351,54.953 38.814,55.109C39.569,62.892 46.087,68.996 54,68.996C60.81,68.996 66.592,64.475 68.553,58.254L69.305,58.254C67.32,64.881 61.211,69.721 54,69.721C45.704,69.721 38.873,63.313 38.097,55.149C37.49,55.035 37.009,54.569 36.866,53.967L32.082,53.968C31.949,54.527 31.522,54.965 30.974,55.116C31.752,67.246 41.779,76.875 54,76.875C65.147,76.875 74.471,68.865 76.615,58.254L77.346,58.254C75.19,69.267 65.545,77.6 54,77.6C41.392,77.6 31.048,67.661 30.254,55.146C29.538,55.001 29,54.364 29,53.6C29,52.836 29.539,52.199 30.254,52.054C31.048,39.539 41.391,29.6 54,29.6C67.003,29.601 77.597,40.172 77.791,53.239C77.793,53.36 77.8,53.48 77.8,53.601ZM77.072,53.239C76.878,40.572 66.606,30.327 53.999,30.327C41.778,30.327 31.75,39.957 30.973,52.085C31.522,52.236 31.95,52.677 32.082,53.239L36.863,53.239C37.005,52.635 37.487,52.167 38.095,52.053C38.871,43.888 45.702,37.48 53.999,37.48C61.069,37.48 67.078,42.134 69.182,48.561L68.422,48.561C66.347,42.541 60.667,38.206 53.999,38.206C46.086,38.206 39.568,44.31 38.813,52.092C39.351,52.25 39.77,52.685 39.9,53.239L45.845,53.239C45.852,53.091 45.86,52.961 45.86,52.961C46.186,48.716 49.706,45.36 53.999,45.36C56.623,45.36 58.955,46.617 60.451,48.561L59.513,48.561C58.148,47.045 56.185,46.085 53.999,46.085C50.01,46.085 46.752,49.263 46.562,53.239L46.562,53.239L77.072,53.239Z" android:pathData="M77.8,53.601C77.8,53.79 77.799,53.963 77.799,53.963L46.564,53.967C46.755,57.942 50.012,61.117 53.999,61.117C56.367,61.117 58.475,59.995 59.842,58.255L60.739,58.255C59.264,60.419 56.794,61.843 53.999,61.843C49.752,61.843 46.262,58.557 45.872,54.375L45.862,54.261C45.855,54.164 45.855,54.065 45.851,53.966L39.9,53.967C39.769,54.519 39.351,54.953 38.814,55.109C39.569,62.892 46.087,68.996 54,68.996C60.81,68.996 66.592,64.475 68.553,58.254L69.305,58.254C67.32,64.881 61.211,69.721 54,69.721C45.704,69.721 38.873,63.313 38.097,55.149C37.49,55.035 37.009,54.569 36.866,53.967L32.082,53.968C31.949,54.527 31.522,54.965 30.974,55.116C31.752,67.246 41.779,76.875 54,76.875C65.147,76.875 74.471,68.865 76.615,58.254L77.346,58.254C75.19,69.267 65.545,77.6 54,77.6C41.392,77.6 31.048,67.661 30.254,55.146C29.538,55.001 29,54.364 29,53.6C29,52.836 29.539,52.199 30.254,52.054C31.048,39.539 41.391,29.6 54,29.6C67.003,29.601 77.597,40.172 77.791,53.239C77.793,53.36 77.8,53.48 77.8,53.601ZM77.072,53.239C76.878,40.572 66.606,30.327 53.999,30.327C41.778,30.327 31.75,39.957 30.973,52.085C31.522,52.236 31.95,52.677 32.082,53.239L36.863,53.239C37.005,52.635 37.487,52.167 38.095,52.053C38.871,43.888 45.702,37.48 53.999,37.48C61.069,37.48 67.078,42.134 69.182,48.561L68.422,48.561C66.347,42.541 60.667,38.206 53.999,38.206C46.086,38.206 39.568,44.31 38.813,52.092C39.351,52.25 39.77,52.685 39.9,53.239L45.845,53.239C45.852,53.091 45.86,52.961 45.86,52.961C46.186,48.716 49.706,45.36 53.999,45.36C56.623,45.36 58.955,46.617 60.451,48.561L59.513,48.561C58.148,47.045 56.185,46.085 53.999,46.085C50.01,46.085 46.752,49.263 46.562,53.239L46.562,53.239L77.072,53.239Z"
android:strokeWidth="1" android:strokeWidth="1"
android:fillColor="#FFB900" android:fillColor="@color/app_icon_foreground"
android:fillType="nonZero" android:fillType="nonZero"
android:strokeColor="#00000000"/> android:strokeColor="#00000000"/>
</vector> </vector>

View File

@ -126,7 +126,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginLeft="8dp" android:layout_marginEnd="8dp"
tools:text="fitness" tools:text="fitness"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_5" app:layout_constraintBottom_toTopOf="@+id/text_address_part_5"
app:layout_constraintStart_toEndOf="@id/barrier_left_address_column_1" app:layout_constraintStart_toEndOf="@id/barrier_left_address_column_1"
@ -218,7 +218,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginLeft="8dp" android:layout_marginEnd="8dp"
tools:text="goals" tools:text="goals"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_6" app:layout_constraintBottom_toTopOf="@+id/text_address_part_6"
app:layout_constraintStart_toEndOf="@id/barrier_left_address_column_2" app:layout_constraintStart_toEndOf="@id/barrier_left_address_column_2"
@ -322,7 +322,7 @@ text_address_part_3, text_address_part_6, text_address_part_9, text_address_part
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
tools:text="Birthday Height: 510,123" tools:text="Birthday Height: 510,123"
android:textSize="20dp" android:textSize="20sp"
android:fontFamily="@font/inconsolata" android:fontFamily="@font/inconsolata"
app:layout_constraintTop_toBottomOf="@id/receive_address_parts" app:layout_constraintTop_toBottomOf="@id/receive_address_parts"
app:layout_constraintBottom_toTopOf="@id/text_message" app:layout_constraintBottom_toTopOf="@id/text_message"
@ -335,14 +335,15 @@ text_address_part_3, text_address_part_6, text_address_part_9, text_address_part
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/TextAppearance.MaterialComponents.Body1" style="@style/TextAppearance.MaterialComponents.Body1"
android:gravity="center" android:gravity="center"
android:text="empowering\neveryone\nwith\neconomic\nfreedom" android:text="@string/backup_slogan"
android:textColor="@color/zcashWhite_50" android:textColor="@color/zcashWhite_50"
android:textSize="56dp" android:textSize="56dp"
android:alpha="0.03" android:alpha="0.03"
app:layout_constraintBottom_toBottomOf="@id/icon_logo" app:layout_constraintBottom_toBottomOf="@id/icon_logo"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/icon_logo" /> app:layout_constraintTop_toTopOf="@id/icon_logo"
tools:ignore="SpUsage" />
<ImageView <ImageView
android:id="@+id/icon_logo" android:id="@+id/icon_logo"
@ -356,7 +357,8 @@ text_address_part_3, text_address_part_6, text_address_part_9, text_address_part
app:layout_constraintVertical_bias="0.33333" app:layout_constraintVertical_bias="0.33333"
app:layout_constraintWidth_percent="0.4053398058" app:layout_constraintWidth_percent="0.4053398058"
android:visibility="invisible" android:visibility="invisible"
app:srcCompat="@drawable/ic_logo_landing" /> app:srcCompat="@drawable/ic_logo_landing"
android:contentDescription="@string/content_description_backup_zcash_logo" />
<!-- Choose release names from here https://en.wikipedia.org/wiki/List_of_woods --> <!-- Choose release names from here https://en.wikipedia.org/wiki/List_of_woods -->
<TextView <TextView
@ -367,7 +369,7 @@ text_address_part_3, text_address_part_6, text_address_part_9, text_address_part
android:gravity="center" android:gravity="center"
android:paddingStart="32dp" android:paddingStart="32dp"
android:paddingEnd="32dp" android:paddingEnd="32dp"
android:text="Store these backup words securely." android:text="@string/backup_instruction_store_words"
android:textColor="@color/zcashWhite" android:textColor="@color/zcashWhite"
app:layout_constraintBottom_toTopOf="@id/guideline_buttons" app:layout_constraintBottom_toTopOf="@id/guideline_buttons"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -378,7 +380,7 @@ text_address_part_3, text_address_part_6, text_address_part_9, text_address_part
android:id="@+id/button_positive" android:id="@+id/button_positive"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Verify" android:text="@string/backup_button_primary"
android:textColor="@color/text_dark" android:textColor="@color/text_dark"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"

View File

@ -54,7 +54,8 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.065" app:layout_constraintVertical_bias="0.065"
app:srcCompat="@drawable/ic_arrow_back_black_24dp" /> app:srcCompat="@drawable/ic_arrow_back_black_24dp"
android:contentDescription="@string/content_description_history_back" />
<View <View
android:id="@+id/back_button_hit_area" android:id="@+id/back_button_hit_area"
@ -66,14 +67,15 @@
app:layout_constraintHorizontal_bias="0.01" app:layout_constraintHorizontal_bias="0.01"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.045" /> app:layout_constraintVertical_bias="0.045"
android:focusable="true" />
<TextView <TextView
android:id="@+id/text_balance_available" android:id="@+id/text_balance_available"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Updating" android:text="@string/history_balance_updating"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/text_light" android:textColor="@color/text_light"
android:visibility="visible" android:visibility="visible"
@ -87,8 +89,8 @@
android:id="@+id/label_balance" android:id="@+id/label_balance"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="4dp" android:layout_marginStart="4dp"
android:text="Available" android:text="@string/history_balance_available"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/colorPrimary" android:textColor="@color/colorPrimary"
app:layout_constraintBaseline_toBaselineOf="@id/text_balance_available" app:layout_constraintBaseline_toBaselineOf="@id/text_balance_available"
@ -99,7 +101,7 @@
android:id="@+id/text_balance_description" android:id="@+id/text_balance_description"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="(enter an amount to send)" android:text="@string/history_instruction_enter_amount"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2" android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
android:textColor="@color/text_light_dimmed" android:textColor="@color/text_light_dimmed"
app:layout_constraintBottom_toBottomOf="@id/back_button" app:layout_constraintBottom_toBottomOf="@id/back_button"
@ -124,9 +126,9 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:text="Your Wallet History" android:text="@string/history_header_transactions"
android:textColor="@color/text_light" android:textColor="@color/text_light"
android:textSize="22dp" android:textSize="22sp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@ -135,7 +137,8 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:text="Shielded address: " android:paddingEnd="8dp"
android:text="@string/history_address_label"
android:textColor="@color/text_light_dimmed" android:textColor="@color/text_light_dimmed"
app:layout_constraintStart_toStartOf="@id/text_header_title" app:layout_constraintStart_toStartOf="@id/text_header_title"
app:layout_constraintTop_toBottomOf="@+id/text_header_title" /> app:layout_constraintTop_toBottomOf="@+id/text_header_title" />
@ -160,7 +163,8 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.15" app:layout_constraintHorizontal_bias="0.15"
app:layout_constraintStart_toEndOf="@id/text_address" app:layout_constraintStart_toEndOf="@id/text_address"
app:layout_constraintTop_toTopOf="@id/label_address" /> app:layout_constraintTop_toTopOf="@id/label_address"
android:contentDescription="@string/content_description_history_copy" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<View <View
@ -176,9 +180,9 @@
android:id="@+id/empty_message" android:id="@+id/empty_message"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="No history yet." android:text="@string/history_empty_text"
android:textColor="@color/text_light" android:textColor="@color/text_light"
android:textSize="18dp" android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"

View File

@ -46,7 +46,7 @@
android:id="@+id/text_balance_available" android:id="@+id/text_balance_available"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Updating" android:text="@string/home_balance_updating"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/text_light" android:textColor="@color/text_light"
android:visibility="visible" android:visibility="visible"
@ -59,8 +59,8 @@
android:id="@+id/label_balance" android:id="@+id/label_balance"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="4dp" android:layout_marginStart="4dp"
android:text="Available" android:text="@string/home_balance_available"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/colorPrimary" android:textColor="@color/colorPrimary"
android:visibility="gone" android:visibility="gone"
@ -72,7 +72,7 @@
android:id="@+id/text_balance_description" android:id="@+id/text_balance_description"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="(enter an amount to send)" android:text="@string/home_instruction_enter_amount"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible" tools:visibility="visible"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2" android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
@ -97,7 +97,8 @@
app:layout_constraintStart_toStartOf="@id/guide_keys" app:layout_constraintStart_toStartOf="@id/guide_keys"
app:layout_constraintTop_toTopOf="@id/guide_keys" app:layout_constraintTop_toTopOf="@id/guide_keys"
app:layout_constraintVertical_chainStyle="spread_inside" app:layout_constraintVertical_chainStyle="spread_inside"
app:layout_constraintWidth_percent="@dimen/calculator_button_width_percent" /> app:layout_constraintWidth_percent="@dimen/calculator_button_width_percent"
tools:ignore="HardcodedText" />
<androidx.appcompat.widget.AppCompatButton <androidx.appcompat.widget.AppCompatButton
android:id="@+id/button_number_pad_2" android:id="@+id/button_number_pad_2"
@ -111,7 +112,8 @@
app:layout_constraintStart_toStartOf="@id/guide_keys" app:layout_constraintStart_toStartOf="@id/guide_keys"
app:layout_constraintTop_toTopOf="@id/guide_keys" app:layout_constraintTop_toTopOf="@id/guide_keys"
app:layout_constraintVertical_chainStyle="spread_inside" app:layout_constraintVertical_chainStyle="spread_inside"
app:layout_constraintWidth_percent="@dimen/calculator_button_width_percent" /> app:layout_constraintWidth_percent="@dimen/calculator_button_width_percent"
tools:ignore="HardcodedText" />
<androidx.appcompat.widget.AppCompatButton <androidx.appcompat.widget.AppCompatButton
android:id="@+id/button_number_pad_3" android:id="@+id/button_number_pad_3"
@ -124,7 +126,8 @@
app:layout_constraintEnd_toEndOf="@id/guide_keys" app:layout_constraintEnd_toEndOf="@id/guide_keys"
app:layout_constraintTop_toTopOf="@id/guide_keys" app:layout_constraintTop_toTopOf="@id/guide_keys"
app:layout_constraintVertical_chainStyle="spread_inside" app:layout_constraintVertical_chainStyle="spread_inside"
app:layout_constraintWidth_percent="@dimen/calculator_button_width_percent" /> app:layout_constraintWidth_percent="@dimen/calculator_button_width_percent"
tools:ignore="HardcodedText" />
<androidx.appcompat.widget.AppCompatButton <androidx.appcompat.widget.AppCompatButton
android:id="@+id/button_number_pad_4" android:id="@+id/button_number_pad_4"
@ -137,7 +140,8 @@
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="@id/guide_keys" app:layout_constraintStart_toStartOf="@id/guide_keys"
app:layout_constraintTop_toBottomOf="@id/button_number_pad_1" app:layout_constraintTop_toBottomOf="@id/button_number_pad_1"
app:layout_constraintWidth_percent="@dimen/calculator_button_width_percent" /> app:layout_constraintWidth_percent="@dimen/calculator_button_width_percent"
tools:ignore="HardcodedText" />
<androidx.appcompat.widget.AppCompatButton <androidx.appcompat.widget.AppCompatButton
android:id="@+id/button_number_pad_5" android:id="@+id/button_number_pad_5"
@ -150,7 +154,8 @@
app:layout_constraintEnd_toEndOf="@id/guide_keys" app:layout_constraintEnd_toEndOf="@id/guide_keys"
app:layout_constraintStart_toStartOf="@id/guide_keys" app:layout_constraintStart_toStartOf="@id/guide_keys"
app:layout_constraintTop_toBottomOf="@id/button_number_pad_2" app:layout_constraintTop_toBottomOf="@id/button_number_pad_2"
app:layout_constraintWidth_percent="@dimen/calculator_button_width_percent" /> app:layout_constraintWidth_percent="@dimen/calculator_button_width_percent"
tools:ignore="HardcodedText" />
<androidx.appcompat.widget.AppCompatButton <androidx.appcompat.widget.AppCompatButton
android:id="@+id/button_number_pad_6" android:id="@+id/button_number_pad_6"
@ -162,7 +167,8 @@
app:layout_constraintDimensionRatio="H,1:1" app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintEnd_toEndOf="@id/guide_keys" app:layout_constraintEnd_toEndOf="@id/guide_keys"
app:layout_constraintTop_toBottomOf="@id/button_number_pad_3" app:layout_constraintTop_toBottomOf="@id/button_number_pad_3"
app:layout_constraintWidth_percent="@dimen/calculator_button_width_percent" /> app:layout_constraintWidth_percent="@dimen/calculator_button_width_percent"
tools:ignore="HardcodedText" />
<androidx.appcompat.widget.AppCompatButton <androidx.appcompat.widget.AppCompatButton
android:id="@+id/button_number_pad_7" android:id="@+id/button_number_pad_7"
@ -175,7 +181,8 @@
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="@id/guide_keys" app:layout_constraintStart_toStartOf="@id/guide_keys"
app:layout_constraintTop_toBottomOf="@id/button_number_pad_4" app:layout_constraintTop_toBottomOf="@id/button_number_pad_4"
app:layout_constraintWidth_percent="@dimen/calculator_button_width_percent" /> app:layout_constraintWidth_percent="@dimen/calculator_button_width_percent"
tools:ignore="HardcodedText" />
<androidx.appcompat.widget.AppCompatButton <androidx.appcompat.widget.AppCompatButton
android:id="@+id/button_number_pad_8" android:id="@+id/button_number_pad_8"
@ -188,7 +195,8 @@
app:layout_constraintEnd_toEndOf="@id/guide_keys" app:layout_constraintEnd_toEndOf="@id/guide_keys"
app:layout_constraintStart_toStartOf="@id/guide_keys" app:layout_constraintStart_toStartOf="@id/guide_keys"
app:layout_constraintTop_toBottomOf="@id/button_number_pad_5" app:layout_constraintTop_toBottomOf="@id/button_number_pad_5"
app:layout_constraintWidth_percent="@dimen/calculator_button_width_percent" /> app:layout_constraintWidth_percent="@dimen/calculator_button_width_percent"
tools:ignore="HardcodedText" />
<androidx.appcompat.widget.AppCompatButton <androidx.appcompat.widget.AppCompatButton
android:id="@+id/button_number_pad_9" android:id="@+id/button_number_pad_9"
@ -200,7 +208,8 @@
app:layout_constraintDimensionRatio="H,1:1" app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintEnd_toEndOf="@id/guide_keys" app:layout_constraintEnd_toEndOf="@id/guide_keys"
app:layout_constraintTop_toBottomOf="@id/button_number_pad_6" app:layout_constraintTop_toBottomOf="@id/button_number_pad_6"
app:layout_constraintWidth_percent="@dimen/calculator_button_width_percent" /> app:layout_constraintWidth_percent="@dimen/calculator_button_width_percent"
tools:ignore="HardcodedText" />
<androidx.appcompat.widget.AppCompatButton <androidx.appcompat.widget.AppCompatButton
android:id="@+id/button_number_pad_decimal" android:id="@+id/button_number_pad_decimal"
@ -208,7 +217,7 @@
android:layout_height="0dp" android:layout_height="0dp"
style="@style/Zcash.TextView.NumberPad" style="@style/Zcash.TextView.NumberPad"
android:paddingBottom="2dp" android:paddingBottom="2dp"
android:text="." android:text="@string/key_decimal"
app:layout_constraintBottom_toBottomOf="@id/guide_keys" app:layout_constraintBottom_toBottomOf="@id/guide_keys"
app:layout_constraintDimensionRatio="H,1:1" app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintHorizontal_chainStyle="packed"
@ -227,14 +236,15 @@
app:layout_constraintEnd_toEndOf="@id/guide_keys" app:layout_constraintEnd_toEndOf="@id/guide_keys"
app:layout_constraintStart_toStartOf="@id/guide_keys" app:layout_constraintStart_toStartOf="@id/guide_keys"
app:layout_constraintTop_toBottomOf="@id/button_number_pad_8" app:layout_constraintTop_toBottomOf="@id/button_number_pad_8"
app:layout_constraintWidth_percent="@dimen/calculator_button_width_percent" /> app:layout_constraintWidth_percent="@dimen/calculator_button_width_percent"
tools:ignore="HardcodedText" />
<androidx.appcompat.widget.AppCompatButton <androidx.appcompat.widget.AppCompatButton
android:id="@+id/button_number_pad_back" android:id="@+id/button_number_pad_back"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
style="@style/Zcash.TextView.NumberPad" style="@style/Zcash.TextView.NumberPad"
android:text="&lt;" android:text="@string/key_backspace"
app:layout_constraintBottom_toBottomOf="@id/guide_keys" app:layout_constraintBottom_toBottomOf="@id/guide_keys"
app:layout_constraintDimensionRatio="H,1:1" app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintEnd_toEndOf="@id/guide_keys" app:layout_constraintEnd_toEndOf="@id/guide_keys"
@ -248,7 +258,8 @@
android:clickable="true" android:clickable="true"
android:background="#D0000000" android:background="#D0000000"
tools:visibility="gone" tools:visibility="gone"
android:elevation="5dp" /> android:elevation="5dp"
android:focusable="true" />
<!-- --> <!-- -->
<!-- Upper Layer --> <!-- Upper Layer -->
@ -284,7 +295,7 @@
android:paddingStart="56dp" android:paddingStart="56dp"
android:paddingEnd="56dp" android:paddingEnd="56dp"
android:textColor="@color/selector_button_text_dark" android:textColor="@color/selector_button_text_dark"
android:textSize="16dp" android:textSize="16sp"
app:layout_constraintTop_toTopOf="@id/lottie_button_loading" app:layout_constraintTop_toTopOf="@id/lottie_button_loading"
app:layout_constraintBottom_toBottomOf="@id/lottie_button_loading" app:layout_constraintBottom_toBottomOf="@id/lottie_button_loading"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
@ -304,7 +315,8 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/icon_profile" app:layout_constraintTop_toTopOf="@id/icon_profile"
app:layout_constraintWidth_percent="0.0887" app:layout_constraintWidth_percent="0.0887"
app:srcCompat="@drawable/ic_address_qr" /> app:srcCompat="@drawable/ic_address_qr"
android:contentDescription="@string/scan_address_title" />
<ImageView <ImageView
android:id="@+id/icon_profile" android:id="@+id/icon_profile"
@ -320,7 +332,8 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.064" app:layout_constraintVertical_bias="0.064"
app:layout_constraintWidth_percent="0.08" app:layout_constraintWidth_percent="0.08"
app:srcCompat="@drawable/ic_account_circle" /> app:srcCompat="@drawable/ic_account_circle"
android:contentDescription="@string/content_description_home_icon_profile" />
<View <View
android:id="@+id/hit_area_receive" android:id="@+id/hit_area_receive"
@ -385,7 +398,7 @@
android:includeFontPadding="false" android:includeFontPadding="false"
tools:text="$0" tools:text="$0"
android:textAppearance="@style/Zcash.TextAppearance.Zec" android:textAppearance="@style/Zcash.TextAppearance.Zec"
android:textSize="72dp" android:textSize="72sp"
android:maxLines="1" android:maxLines="1"
android:paddingStart="16dp" android:paddingStart="16dp"
android:paddingEnd="16dp" android:paddingEnd="16dp"
@ -408,8 +421,9 @@
android:background="@drawable/background_banner" android:background="@drawable/background_banner"
android:paddingBottom="8dp" android:paddingBottom="8dp"
android:paddingStart="16dp" android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="8dp" android:paddingTop="8dp"
android:text="No Balance" android:text="@string/home_no_balance"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/text_light" android:textColor="@color/text_light"
app:layout_constraintEnd_toEndOf="@id/icon_profile" app:layout_constraintEnd_toEndOf="@id/icon_profile"
@ -422,7 +436,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:text="Fund Now" android:text="@string/home_instruction_fund_now"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/colorPrimary" android:textColor="@color/colorPrimary"
app:layout_constraintBaseline_toBaselineOf="@id/text_banner_message" app:layout_constraintBaseline_toBaselineOf="@id/text_banner_message"

View File

@ -49,7 +49,7 @@
android:gravity="center" android:gravity="center"
android:paddingStart="32dp" android:paddingStart="32dp"
android:paddingEnd="32dp" android:paddingEnd="32dp"
android:text="Welcome to the ECC Wallet!" android:text="@string/landing_title"
android:textColor="@color/zcashWhite" android:textColor="@color/zcashWhite"
app:layout_constraintBottom_toTopOf="@id/guideline_buttons" app:layout_constraintBottom_toTopOf="@id/guideline_buttons"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -61,7 +61,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/Zcash.Button.OutlinedButton" style="@style/Zcash.Button.OutlinedButton"
android:text="Restore" android:text="@string/landing_button_secondary"
android:textColor="@color/text_light" android:textColor="@color/text_light"
app:layout_constraintEnd_toStartOf="@id/button_positive" app:layout_constraintEnd_toStartOf="@id/button_positive"
app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintHorizontal_chainStyle="packed"
@ -73,7 +73,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:text="New" android:text="@string/landing_button_primary"
android:textColor="@color/text_dark" android:textColor="@color/text_dark"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/button_negative" app:layout_constraintStart_toEndOf="@id/button_negative"

View File

@ -62,7 +62,8 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.065" app:layout_constraintVertical_bias="0.065"
app:layout_constraintWidth_percent="0.08" app:layout_constraintWidth_percent="0.08"
app:srcCompat="@drawable/ic_cancel" /> app:srcCompat="@drawable/ic_cancel"
android:contentDescription="@string/content_description_profile_back" />
<ImageView <ImageView
android:id="@+id/icon_settings" android:id="@+id/icon_settings"
@ -78,7 +79,8 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.065" app:layout_constraintVertical_bias="0.065"
app:layout_constraintWidth_percent="0.08" app:layout_constraintWidth_percent="0.08"
app:srcCompat="@drawable/ic_settings" /> app:srcCompat="@drawable/ic_settings"
android:contentDescription="@string/content_description_profile_settings" />
<ImageView <ImageView
android:id="@+id/icon_profile" android:id="@+id/icon_profile"
@ -92,7 +94,8 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.212" app:layout_constraintVertical_bias="0.212"
app:layout_constraintWidth_percent="0.4" app:layout_constraintWidth_percent="0.4"
app:srcCompat="@drawable/ic_profile_zebra_01" /> app:srcCompat="@drawable/ic_profile_zebra_01"
android:contentDescription="@string/content_description_profile_zebra" />
<View <View
android:id="@+id/hit_area_settings" android:id="@+id/hit_area_settings"
@ -138,7 +141,8 @@
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.15" app:layout_constraintHorizontal_bias="0.15"
app:layout_constraintStart_toEndOf="@id/text_address" app:layout_constraintStart_toEndOf="@id/text_address"
app:layout_constraintTop_toTopOf="@id/text_address" /> app:layout_constraintTop_toTopOf="@id/text_address"
android:contentDescription="@string/content_description_profile_copy" />
<View <View
android:id="@+id/hit_area_address" android:id="@+id/hit_area_address"
@ -214,7 +218,8 @@
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/guideline_content_end" app:layout_constraintEnd_toEndOf="@id/guideline_content_end"
app:layout_constraintStart_toStartOf="@id/guideline_content_start" app:layout_constraintStart_toStartOf="@id/guideline_content_start"
app:layout_constraintTop_toBottomOf="@id/button_logs" /> app:layout_constraintTop_toBottomOf="@id/button_logs"
tools:ignore="RtlSymmetry" />
<TextView <TextView
android:id="@+id/text_version" android:id="@+id/text_version"
@ -222,7 +227,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:elevation="6dp" android:elevation="6dp"
android:text="@string/profile_app_version" tools:text="@string/profile_app_version"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/text_light_dimmed" android:textColor="@color/text_light_dimmed"
app:layout_constraintBaseline_toBaselineOf="@id/text_banner_message" app:layout_constraintBaseline_toBaselineOf="@id/text_banner_message"

View File

@ -53,16 +53,15 @@
<!-- app:layout_constraintVertical_bias="0.3" />--> <!-- app:layout_constraintVertical_bias="0.3" />-->
<ImageView <ImageView
android:id="@+id/background_qr" android:id="@+id/background_qr"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:contentDescription="@string/content_description_receive_qr_background"
android:scaleType="fitXY" android:scaleType="fitXY"
android:src="@drawable/ic_background_qr" android:src="@drawable/ic_background_qr"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintEnd_toStartOf="@id/spacer_bottom_right" app:layout_constraintEnd_toStartOf="@id/spacer_bottom_right"
app:layout_constraintStart_toEndOf="@id/spacer_bottom_left" app:layout_constraintStart_toEndOf="@id/spacer_bottom_left"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
@ -73,21 +72,23 @@
android:id="@+id/receive_qr_code" android:id="@+id/receive_qr_code"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:contentDescription="@string/content_description_receive_qr_code"
android:onClick="copyAddress" android:onClick="copyAddress"
android:scaleType="fitCenter" android:scaleType="fitCenter"
tools:visibility="visible"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintWidth_percent="0.625"
app:layout_constraintStart_toStartOf="@id/background_qr"
app:layout_constraintEnd_toEndOf="@id/background_qr"
app:layout_constraintTop_toTopOf="@id/background_qr"
app:layout_constraintBottom_toBottomOf="@id/background_qr" app:layout_constraintBottom_toBottomOf="@id/background_qr"
tools:background="@color/zcashWhite" /> app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintEnd_toEndOf="@id/background_qr"
app:layout_constraintStart_toStartOf="@id/background_qr"
app:layout_constraintTop_toTopOf="@id/background_qr"
app:layout_constraintWidth_percent="0.625"
tools:background="@color/zcashWhite"
tools:visibility="visible" />
<ImageView <ImageView
android:id="@+id/icon_qr_logo" android:id="@+id/icon_qr_logo"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:contentDescription="@string/content_description_receive_qr_logo"
android:onClick="copyAddress" android:onClick="copyAddress"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:src="@drawable/ic_shield_address" android:src="@drawable/ic_shield_address"
@ -102,12 +103,12 @@
android:id="@+id/receive_title" android:id="@+id/receive_title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/address_label_shielded"
android:drawableEnd="@drawable/ic_content_copy" android:drawableEnd="@drawable/ic_content_copy"
android:drawablePadding="16dp" android:drawablePadding="16dp"
android:text="@string/receive_address_label_shielded"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/text_light" android:textColor="@color/text_light"
android:textSize="20dp" android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/spacer_title" /> app:layout_constraintTop_toBottomOf="@id/spacer_title" />
@ -119,10 +120,10 @@
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:onClick="copyAddress" android:onClick="copyAddress"
app:layout_constraintStart_toStartOf="@id/background_qr"
app:layout_constraintEnd_toEndOf="@id/background_qr"
app:layout_constraintTop_toBottomOf="@id/background_qr"
app:layout_constraintBottom_toBottomOf="@id/receive_address_parts" app:layout_constraintBottom_toBottomOf="@id/receive_address_parts"
app:layout_constraintEnd_toEndOf="@id/background_qr"
app:layout_constraintStart_toStartOf="@id/background_qr"
app:layout_constraintTop_toBottomOf="@id/background_qr"
tools:background="@color/spacer" /> tools:background="@color/spacer" />
<!-- Back Button --> <!-- Back Button -->
@ -132,14 +133,15 @@
android:layout_height="0dp" android:layout_height="0dp"
android:tint="@color/text_light_dimmed" android:tint="@color/text_light_dimmed"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.088" app:layout_constraintHorizontal_bias="0.088"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintWidth_percent="0.08"
app:layout_constraintVertical_bias="0.065" app:layout_constraintVertical_bias="0.065"
app:srcCompat="@drawable/ic_cancel" /> app:layout_constraintWidth_percent="0.08"
app:srcCompat="@drawable/ic_cancel"
android:contentDescription="@string/content_description_receive_back" />
<View <View
android:id="@+id/back_button_hit_area" android:id="@+id/back_button_hit_area"
@ -147,6 +149,7 @@
android:layout_height="68dp" android:layout_height="68dp"
android:layout_marginStart="24dp" android:layout_marginStart="24dp"
android:clickable="true" android:clickable="true"
android:focusable="true"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
@ -157,17 +160,16 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/TextAppearance.MaterialComponents.Headline6" style="@style/TextAppearance.MaterialComponents.Headline6"
android:autoSizeTextType="uniform"
android:maxLines="1" android:maxLines="1"
android:text="@string/receive_address_title" android:text="@string/receive_address_title"
android:textColor="@color/text_light" android:textColor="@color/text_light"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="@id/back_button" app:layout_constraintBottom_toBottomOf="@id/back_button"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/back_button_hit_area" app:layout_constraintStart_toEndOf="@id/back_button_hit_area"
app:layout_constraintTop_toTopOf="@id/back_button" /> app:layout_constraintTop_toTopOf="@id/back_button" />
<!-- Address parts --> <!-- Address parts -->
<!-- Someday, there will be an advanced VirtualLayout that helps us do this without nesting but for now, this seems to be the only clean way to center all the fields --> <!-- Someday, there will be an advanced VirtualLayout that helps us do this without nesting but for now, this seems to be the only clean way to center all the fields -->
@ -180,95 +182,95 @@
app:layout_constraintEnd_toEndOf="@id/receive_title" app:layout_constraintEnd_toEndOf="@id/receive_title"
app:layout_constraintStart_toStartOf="@id/receive_title" app:layout_constraintStart_toStartOf="@id/receive_title"
app:layout_constraintTop_toBottomOf="@id/receive_title"> app:layout_constraintTop_toBottomOf="@id/receive_title">
<TextView <TextView
android:id="@+id/text_address_part_1" android:id="@+id/text_address_part_1"
style="@style/Zcash.TextAppearance.AddressPart"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text=" ztestsaplin" style="@style/Zcash.TextAppearance.AddressPart"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_3" app:layout_constraintBottom_toTopOf="@+id/text_address_part_3"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" app:layout_constraintVertical_bias="0.0"
app:layout_constraintVertical_chainStyle="packed" /> app:layout_constraintVertical_chainStyle="packed"
tools:text=" ztestsaplin" />
<TextView <TextView
android:id="@+id/text_address_part_3" android:id="@+id/text_address_part_3"
style="@style/Zcash.TextAppearance.AddressPart"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:text=" jceuu9s2p6t" style="@style/Zcash.TextAppearance.AddressPart"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_5" app:layout_constraintBottom_toTopOf="@+id/text_address_part_5"
app:layout_constraintStart_toStartOf="@id/text_address_part_1" app:layout_constraintStart_toStartOf="@id/text_address_part_1"
app:layout_constraintTop_toBottomOf="@id/text_address_part_1" /> app:layout_constraintTop_toBottomOf="@id/text_address_part_1"
tools:text=" jceuu9s2p6t" />
<TextView <TextView
android:id="@+id/text_address_part_5" android:id="@+id/text_address_part_5"
style="@style/Zcash.TextAppearance.AddressPart"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:text=" 7u7uarqls7d" style="@style/Zcash.TextAppearance.AddressPart"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_7" app:layout_constraintBottom_toTopOf="@+id/text_address_part_7"
app:layout_constraintStart_toStartOf="@id/text_address_part_1" app:layout_constraintStart_toStartOf="@id/text_address_part_1"
app:layout_constraintTop_toBottomOf="@id/text_address_part_3" /> app:layout_constraintTop_toBottomOf="@id/text_address_part_3"
tools:text=" 7u7uarqls7d" />
<TextView <TextView
android:id="@+id/text_address_part_7" android:id="@+id/text_address_part_7"
style="@style/Zcash.TextAppearance.AddressPart"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:text=" rzq85xggu56" style="@style/Zcash.TextAppearance.AddressPart"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@id/text_address_part_1" app:layout_constraintStart_toStartOf="@id/text_address_part_1"
app:layout_constraintTop_toBottomOf="@id/text_address_part_5" /> app:layout_constraintTop_toBottomOf="@id/text_address_part_5"
tools:text=" rzq85xggu56" />
<TextView <TextView
android:id="@+id/text_address_part_2" android:id="@+id/text_address_part_2"
style="@style/Zcash.TextAppearance.AddressPart"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginLeft="8dp" style="@style/Zcash.TextAppearance.AddressPart"
android:text=" g1mwjzlg62j"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_4" app:layout_constraintBottom_toTopOf="@+id/text_address_part_4"
app:layout_constraintStart_toEndOf="@id/barrier_left_address_column" app:layout_constraintStart_toEndOf="@id/barrier_left_address_column"
app:layout_constraintTop_toTopOf="@id/text_address_part_1" /> app:layout_constraintTop_toTopOf="@id/text_address_part_1"
tools:text=" g1mwjzlg62j" />
<TextView <TextView
android:id="@+id/text_address_part_4" android:id="@+id/text_address_part_4"
style="@style/Zcash.TextAppearance.AddressPart"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:text=" wns6qxwec6v" style="@style/Zcash.TextAppearance.AddressPart"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_6" app:layout_constraintBottom_toTopOf="@+id/text_address_part_6"
app:layout_constraintStart_toStartOf="@id/text_address_part_2" app:layout_constraintStart_toStartOf="@id/text_address_part_2"
app:layout_constraintTop_toBottomOf="@id/text_address_part_2" /> app:layout_constraintTop_toBottomOf="@id/text_address_part_2"
tools:text=" wns6qxwec6v" />
<TextView <TextView
android:id="@+id/text_address_part_6" android:id="@+id/text_address_part_6"
style="@style/Zcash.TextAppearance.AddressPart"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:text=" gtg3tpgqxjd" style="@style/Zcash.TextAppearance.AddressPart"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_8" app:layout_constraintBottom_toTopOf="@+id/text_address_part_8"
app:layout_constraintStart_toStartOf="@id/text_address_part_2" app:layout_constraintStart_toStartOf="@id/text_address_part_2"
app:layout_constraintTop_toBottomOf="@id/text_address_part_4" /> app:layout_constraintTop_toBottomOf="@id/text_address_part_4"
tools:text=" gtg3tpgqxjd" />
<TextView <TextView
android:id="@+id/text_address_part_8" android:id="@+id/text_address_part_8"
style="@style/Zcash.TextAppearance.AddressPart"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:text=" k904xderng6" style="@style/Zcash.TextAppearance.AddressPart"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@id/text_address_part_2" app:layout_constraintStart_toStartOf="@id/text_address_part_2"
app:layout_constraintTop_toBottomOf="@id/text_address_part_6" /> app:layout_constraintTop_toBottomOf="@id/text_address_part_6"
tools:text=" k904xderng6" />
<androidx.constraintlayout.widget.Barrier <androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier_left_address_column" android:id="@+id/barrier_left_address_column"

View File

@ -141,6 +141,7 @@
android:id="@+id/scan_frame" android:id="@+id/scan_frame"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:contentDescription="@string/content_description_scan_frame"
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:src="@drawable/ic_scan_frame" android:src="@drawable/ic_scan_frame"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
@ -155,13 +156,13 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginTop="32dp" android:layout_marginTop="32dp"
android:ellipsize="end"
android:fontFamily="@font/inconsolata" android:fontFamily="@font/inconsolata"
android:gravity="center" android:gravity="center"
android:textColor="@color/text_light_dimmed"
android:textSize="16dp"
android:ellipsize="end"
android:paddingBottom="16dp" android:paddingBottom="16dp"
android:paddingTop="8dp" android:paddingTop="8dp"
android:textColor="@color/text_light_dimmed"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="@id/scan_frame" app:layout_constraintEnd_toEndOf="@id/scan_frame"
app:layout_constraintStart_toStartOf="@id/scan_frame" app:layout_constraintStart_toStartOf="@id/scan_frame"
app:layout_constraintTop_toBottomOf="@id/scan_frame" app:layout_constraintTop_toBottomOf="@id/scan_frame"
@ -169,25 +170,28 @@
<ImageView <ImageView
android:id="@+id/back_button" android:id="@+id/back_button"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="0dp"
android:tint="@color/text_light" android:contentDescription="@string/content_description_scan_back"
android:tint="@color/text_light_dimmed"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.05" app:layout_constraintHorizontal_bias="0.088"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.065" app:layout_constraintVertical_bias="0.065"
app:srcCompat="@drawable/ic_close_black_24dp" /> app:layout_constraintWidth_percent="0.08"
app:srcCompat="@drawable/ic_cancel" />
<View <View
android:id="@+id/back_button_hit_area" android:id="@+id/back_button_hit_area"
android:layout_width="56dp" android:layout_width="68dp"
android:layout_height="56dp" android:layout_height="68dp"
android:layout_marginStart="24dp"
android:clickable="true" android:clickable="true"
android:focusable="true"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.01"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.045" /> app:layout_constraintVertical_bias="0.045" />
@ -197,10 +201,10 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/TextAppearance.MaterialComponents.Headline6" style="@style/TextAppearance.MaterialComponents.Headline6"
android:autoSizeTextType="uniform"
android:maxLines="1" android:maxLines="1"
android:text="@string/scan_address_title" android:text="@string/scan_address_title"
android:textColor="@color/text_light" android:textColor="@color/text_light"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="@id/back_button" app:layout_constraintBottom_toBottomOf="@id/back_button"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/back_button_hit_area" app:layout_constraintStart_toEndOf="@id/back_button_hit_area"

View File

@ -19,6 +19,7 @@
android:id="@+id/back_button" android:id="@+id/back_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:contentDescription="@string/content_description_send_back"
android:tint="@color/text_light" android:tint="@color/text_light"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -33,6 +34,7 @@
android:layout_width="56dp" android:layout_width="56dp"
android:layout_height="56dp" android:layout_height="56dp"
android:clickable="true" android:clickable="true"
android:focusable="true"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.01" app:layout_constraintHorizontal_bias="0.01"
@ -52,7 +54,6 @@
android:paddingEnd="16dp" android:paddingEnd="16dp"
android:paddingStart="16dp" android:paddingStart="16dp"
android:paddingTop="0dp" android:paddingTop="0dp"
android:text="$20.1"
android:textAppearance="@style/Zcash.TextAppearance.Zec" android:textAppearance="@style/Zcash.TextAppearance.Zec"
android:textSize="200sp" android:textSize="200sp"
app:autoSizeMaxTextSize="40sp" app:autoSizeMaxTextSize="40sp"
@ -62,14 +63,15 @@
app:layout_constraintEnd_toStartOf="@id/button_send" app:layout_constraintEnd_toStartOf="@id/button_send"
app:layout_constraintStart_toEndOf="@id/spacer_title" app:layout_constraintStart_toEndOf="@id/spacer_title"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" /> app:layout_constraintVertical_chainStyle="packed"
tools:text="$20.1" />
<TextView <TextView
android:id="@+id/text_banner_message_start" android:id="@+id/text_banner_message_start"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:elevation="6dp" android:elevation="6dp"
android:text="from your " android:text="@string/send_fund_source_prefix"
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption" android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
android:textColor="@color/text_light" android:textColor="@color/text_light"
app:layout_constraintBottom_toTopOf="@id/guideline_content_top" app:layout_constraintBottom_toTopOf="@id/guideline_content_top"
@ -83,7 +85,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:elevation="6dp" android:elevation="6dp"
android:text="shielded" android:text="@string/send_fund_source_highlight"
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption" android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
android:textColor="@color/colorPrimary" android:textColor="@color/colorPrimary"
app:layout_constraintBaseline_toBaselineOf="@id/text_banner_message_start" app:layout_constraintBaseline_toBaselineOf="@id/text_banner_message_start"
@ -95,7 +97,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:elevation="6dp" android:elevation="6dp"
android:text=" wallet" android:text="@string/send_fund_source_suffix"
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption" android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
android:textColor="@color/text_light" android:textColor="@color/text_light"
app:layout_constraintBaseline_toBaselineOf="@id/text_banner_message_start" app:layout_constraintBaseline_toBaselineOf="@id/text_banner_message_start"
@ -107,6 +109,7 @@
android:id="@+id/image_down_arrow" android:id="@+id/image_down_arrow"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:contentDescription="@string/content_description_send_arrow"
android:tint="@color/colorPrimary" android:tint="@color/colorPrimary"
android:visibility="gone" android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/text_banner_message_end" app:layout_constraintBottom_toBottomOf="@id/text_banner_message_end"
@ -127,11 +130,16 @@
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/button_send" android:id="@+id/button_send"
android:layout_width="74dp" android:layout_width="wrap_content"
android:layout_height="38dp" android:layout_height="38dp"
android:text="Send"
android:textColor="@color/text_dark"
android:backgroundTint="@color/selector_primary_button_activatable" android:backgroundTint="@color/selector_primary_button_activatable"
android:maxLines="1"
android:paddingEnd="2dp"
android:paddingStart="2dp"
android:text="@string/send_button_primary"
android:textColor="@color/text_dark"
app:autoSizeMinTextSize="6sp"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="@id/back_button" app:layout_constraintBottom_toBottomOf="@id/back_button"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.95" app:layout_constraintHorizontal_bias="0.95"
@ -143,16 +151,16 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
app:autoSizeMaxTextSize="12sp"
app:autoSizeMinTextSize="6sp"
app:autoSizeTextType="uniform"
android:fontFamily="@font/inconsolata" android:fontFamily="@font/inconsolata"
android:gravity="center"
android:maxLines="1"
android:paddingEnd="8dp" android:paddingEnd="8dp"
android:paddingStart="8dp" android:paddingStart="8dp"
android:textColor="@color/zcashRed" android:textColor="@color/zcashRed"
android:maxLines="1"
android:textSize="14sp" android:textSize="14sp"
android:gravity="center" app:autoSizeMaxTextSize="12sp"
app:autoSizeMinTextSize="6sp"
app:autoSizeTextType="uniform"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_banner_message_start" app:layout_constraintTop_toBottomOf="@id/text_banner_message_start"
@ -176,9 +184,9 @@
android:id="@+id/spacer_lower_content" android:id="@+id/spacer_lower_content"
android:layout_width="1dp" android:layout_width="1dp"
android:layout_height="0dp" android:layout_height="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.04" app:layout_constraintHeight_percent="0.04"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/check_include_address" /> app:layout_constraintTop_toBottomOf="@+id/check_include_address" />
<!-- Input: Address --> <!-- Input: Address -->
@ -186,7 +194,7 @@
android:id="@+id/text_layout_address" android:id="@+id/text_layout_address"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="To" android:hint="@string/send_address_hint"
android:theme="@style/Zcash.Overlay.TextInputLayout" android:theme="@style/Zcash.Overlay.TextInputLayout"
app:endIconDrawable="@drawable/ic_qr_scan" app:endIconDrawable="@drawable/ic_qr_scan"
app:endIconMode="custom" app:endIconMode="custom"
@ -215,7 +223,7 @@
android:id="@+id/text_layout_memo" android:id="@+id/text_layout_memo"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="Memo" android:hint="@string/send_memo_hint"
android:theme="@style/Zcash.Overlay.TextInputLayout" android:theme="@style/Zcash.Overlay.TextInputLayout"
app:endIconMode="clear_text" app:endIconMode="clear_text"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -270,7 +278,7 @@
android:padding="0dp" android:padding="0dp"
android:scaleX="0.84" android:scaleX="0.84"
android:scaleY="0.84" android:scaleY="0.84"
android:text="include reply-to" android:text="@string/send_checkbox_include_address"
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption" android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
android:textColor="@color/text_light_dimmed" android:textColor="@color/text_light_dimmed"
android:textSize="14sp" android:textSize="14sp"
@ -298,7 +306,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="1dp" android:padding="1dp"
android:text="On clipboard" android:text="@string/send_history_clipboard"
android:textAllCaps="true" android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2" android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -320,6 +328,7 @@
android:id="@+id/image_shield" android:id="@+id/image_shield"
android:layout_width="16dp" android:layout_width="16dp"
android:layout_height="16dp" android:layout_height="16dp"
android:contentDescription="@string/content_description_send_shield"
android:src="@drawable/ic_shielded" android:src="@drawable/ic_shielded"
android:tint="@color/colorPrimary" android:tint="@color/colorPrimary"
app:layout_constraintBottom_toBottomOf="@id/clipboard_address_label" app:layout_constraintBottom_toBottomOf="@id/clipboard_address_label"
@ -333,7 +342,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="4dp" android:layout_marginStart="4dp"
android:text="Unknown" android:text="@string/unknown"
android:textColor="@color/colorPrimary" android:textColor="@color/colorPrimary"
app:layout_constraintBottom_toTopOf="@id/clipboard_address" app:layout_constraintBottom_toTopOf="@id/clipboard_address"
app:layout_constraintStart_toEndOf="@id/image_shield" app:layout_constraintStart_toEndOf="@id/image_shield"
@ -344,16 +353,17 @@
android:id="@+id/clipboard_address" android:id="@+id/clipboard_address"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="zasdfaksfjaslfjaslfkjaslk;kfjaslkfjasld;kfjaslfjdasflja"
android:textColor="@color/text_light" android:textColor="@color/text_light"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@id/image_shield" app:layout_constraintStart_toStartOf="@id/image_shield"
app:layout_constraintTop_toBottomOf="@id/clipboard_address_label" /> app:layout_constraintTop_toBottomOf="@id/clipboard_address_label"
tools:text="zasdfaksfjaslfjaslfkjaslk;kfjaslkfjasld;kfjaslfjdasflja" />
<ImageView <ImageView
android:id="@+id/image_clipboard_address_selected" android:id="@+id/image_clipboard_address_selected"
android:layout_width="22dp" android:layout_width="22dp"
android:layout_height="22dp" android:layout_height="22dp"
android:contentDescription="@string/content_description_send_selected"
android:src="@drawable/ic_check_shielded" android:src="@drawable/ic_check_shielded"
android:tint="@color/colorPrimary" android:tint="@color/colorPrimary"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
@ -383,7 +393,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="1dp" android:padding="1dp"
android:text="last used" android:text="@string/send_history_last"
android:textAllCaps="true" android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2" android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -403,6 +413,7 @@
android:id="@+id/image_last_used_shield" android:id="@+id/image_last_used_shield"
android:layout_width="16dp" android:layout_width="16dp"
android:layout_height="16dp" android:layout_height="16dp"
android:contentDescription="@string/content_description_send_shield"
android:src="@drawable/ic_shielded" android:src="@drawable/ic_shielded"
android:tint="@color/colorPrimary" android:tint="@color/colorPrimary"
app:layout_constraintBottom_toBottomOf="@id/last_used_address_label" app:layout_constraintBottom_toBottomOf="@id/last_used_address_label"
@ -417,7 +428,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="4dp" android:layout_marginStart="4dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:text="Unknown" android:text="@string/unknown"
android:textColor="@color/colorPrimary" android:textColor="@color/colorPrimary"
app:layout_constraintBottom_toTopOf="@id/last_used_address" app:layout_constraintBottom_toTopOf="@id/last_used_address"
app:layout_constraintStart_toEndOf="@id/image_last_used_shield" app:layout_constraintStart_toEndOf="@id/image_last_used_shield"
@ -428,16 +439,17 @@
android:id="@+id/last_used_address" android:id="@+id/last_used_address"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="zasdfaksfjaslfjaslfkjaslk;kfjaslkfjasld;kfjaslfjdasflja"
android:textColor="@color/text_light" android:textColor="@color/text_light"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@id/image_last_used_shield" app:layout_constraintStart_toStartOf="@id/image_last_used_shield"
app:layout_constraintTop_toBottomOf="@id/last_used_address_label" /> app:layout_constraintTop_toBottomOf="@id/last_used_address_label"
tools:text="zasdfaksfjaslfjaslfkjaslk;kfjaslkfjasld;kfjaslfjdasflja" />
<ImageView <ImageView
android:id="@+id/image_last_used_address_selected" android:id="@+id/image_last_used_address_selected"
android:layout_width="22dp" android:layout_width="22dp"
android:layout_height="22dp" android:layout_height="22dp"
android:contentDescription="@string/content_description_send_selected"
android:src="@drawable/ic_check_shielded" android:src="@drawable/ic_check_shielded"
android:tint="@color/colorPrimary" android:tint="@color/colorPrimary"
android:visibility="gone" android:visibility="gone"
@ -467,6 +479,7 @@
android:id="@+id/image_scan_qr" android:id="@+id/image_scan_qr"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:contentDescription="@string/content_description_send_scan_qr"
android:paddingBottom="24dp" android:paddingBottom="24dp"
android:paddingEnd="1dp" android:paddingEnd="1dp"
android:paddingStart="6dp" android:paddingStart="6dp"

View File

@ -25,36 +25,40 @@
<!-- Back Button --> <!-- Back Button -->
<ImageView <ImageView
android:id="@+id/back_button" android:id="@+id/back_button"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="0dp"
android:contentDescription="@string/content_description_send_final_back"
android:tint="@color/zcashBlack_87"
android:visibility="gone" android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.05" app:layout_constraintHorizontal_bias="0.088"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.065" app:layout_constraintVertical_bias="0.065"
app:srcCompat="@drawable/ic_close_black_24dp" app:layout_constraintWidth_percent="0.08"
app:srcCompat="@drawable/ic_cancel"
tools:visibility="visible" /> tools:visibility="visible" />
<View <View
android:id="@+id/back_button_hit_area" android:id="@+id/back_button_hit_area"
android:layout_width="56dp" android:layout_width="68dp"
android:layout_height="56dp" android:layout_height="68dp"
android:layout_marginStart="24dp"
android:clickable="true" android:clickable="true"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.01"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.045" /> app:layout_constraintVertical_bias="0.045"
android:focusable="true" />
<TextView <TextView
android:id="@+id/text_confirmation" android:id="@+id/text_confirmation"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/TextAppearance.MaterialComponents.Headline5" style="@style/TextAppearance.MaterialComponents.Headline5"
android:autoSizeTextType="uniform" app:autoSizeTextType="uniform"
android:gravity="center" android:gravity="center"
android:maxLines="3" android:maxLines="3"
android:paddingEnd="16dp" android:paddingEnd="16dp"
@ -87,15 +91,14 @@
android:id="@+id/error_message" android:id="@+id/error_message"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Oops! Failed to send due to insufficient funds!"
android:textColor="@color/text_dark"
android:gravity="center" android:gravity="center"
tools:text="Oops! Failed to send due to insufficient funds!"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
app:layout_constraintTop_toBottomOf="@id/lottie_sending" android:textColor="@color/text_dark"
app:layout_constraintBottom_toTopOf="@id/button_primary" app:layout_constraintBottom_toTopOf="@id/button_primary"
app:layout_constraintStart_toStartOf="@id/button_primary"
app:layout_constraintEnd_toEndOf="@id/button_primary" app:layout_constraintEnd_toEndOf="@id/button_primary"
/> app:layout_constraintStart_toStartOf="@id/button_primary"
app:layout_constraintTop_toBottomOf="@id/lottie_sending" />
<!-- <TextView--> <!-- <TextView-->
<!-- android:id="@+id/text_status"--> <!-- android:id="@+id/text_status"-->
<!-- android:layout_width="wrap_content"--> <!-- android:layout_width="wrap_content"-->
@ -141,9 +144,9 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/Zcash.Button.OutlinedButton" style="@style/Zcash.Button.OutlinedButton"
android:padding="20dp" android:padding="20dp"
android:translationY="-6dp"
android:text="@string/cancel" android:text="@string/cancel"
android:textColor="@color/text_dark" android:textColor="@color/text_dark"
android:translationY="-6dp"
app:layout_constraintBottom_toTopOf="@id/button_secondary" app:layout_constraintBottom_toTopOf="@id/button_secondary"
app:layout_constraintEnd_toEndOf="@id/guide_keys" app:layout_constraintEnd_toEndOf="@id/guide_keys"
app:layout_constraintStart_toStartOf="@id/guide_keys" app:layout_constraintStart_toStartOf="@id/guide_keys"

View File

@ -29,6 +29,7 @@
android:id="@+id/icon_exit" android:id="@+id/icon_exit"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:contentDescription="@string/content_description_settings_back"
android:elevation="6dp" android:elevation="6dp"
android:tint="@color/text_light_dimmed" android:tint="@color/text_light_dimmed"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
@ -47,10 +48,10 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/TextAppearance.MaterialComponents.Headline6" style="@style/TextAppearance.MaterialComponents.Headline6"
android:autoSizeTextType="uniform"
android:maxLines="1" android:maxLines="1"
android:text="@string/settings_change_lightwalletd_server" android:text="@string/settings_change_lightwalletd_server"
android:textColor="@color/text_light" android:textColor="@color/text_light"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="@id/icon_exit" app:layout_constraintBottom_toBottomOf="@id/icon_exit"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/hit_area_exit" app:layout_constraintStart_toEndOf="@id/hit_area_exit"
@ -138,17 +139,18 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/zcashWhite_24" android:background="@color/zcashWhite_24"
android:clickable="true" android:clickable="true"
android:elevation="8dp"
android:focusableInTouchMode="true" android:focusableInTouchMode="true"
android:elevation="8dp" /> android:focusable="true" />
<ProgressBar <ProgressBar
android:id="@+id/loading_progress" android:id="@+id/loading_progress"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="12dp" android:layout_marginEnd="12dp"
app:layout_constraintTop_toTopOf="@id/button_reset"
app:layout_constraintBottom_toBottomOf="@id/button_reset" app:layout_constraintBottom_toBottomOf="@id/button_reset"
app:layout_constraintStart_toStartOf="@id/icon_exit"/> app:layout_constraintStart_toStartOf="@id/icon_exit"
app:layout_constraintTop_toTopOf="@id/button_reset" />
<androidx.constraintlayout.widget.Group <androidx.constraintlayout.widget.Group
android:id="@+id/group_loading" android:id="@+id/group_loading"
@ -157,5 +159,4 @@
app:constraint_referenced_ids="loading_progress,loading_view" /> app:constraint_referenced_ids="loading_progress,loading_view" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -71,6 +71,7 @@
android:id="@+id/close_button" android:id="@+id/close_button"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:contentDescription="@string/content_description_transaction_details_back"
android:tint="@color/zcashWhite_40" android:tint="@color/zcashWhite_40"
app:layout_constraintBottom_toTopOf="@id/text_timestamp" app:layout_constraintBottom_toTopOf="@id/text_timestamp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
@ -84,6 +85,7 @@
android:layout_width="56dp" android:layout_width="56dp"
android:layout_height="56dp" android:layout_height="56dp"
android:clickable="true" android:clickable="true"
android:focusable="true"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.01" app:layout_constraintHorizontal_bias="0.01"
@ -108,13 +110,13 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="12dp" android:layout_marginEnd="12dp"
android:autoSizeMaxTextSize="18sp"
android:autoSizeMinTextSize="6dp"
android:autoSizeTextType="uniform"
android:gravity="bottom" android:gravity="bottom"
android:maxLines="1" android:maxLines="1"
android:textColor="@color/tx_text_light_dimmed" android:textColor="@color/tx_text_light_dimmed"
android:textSize="18sp" android:textSize="18sp"
app:autoSizeMaxTextSize="18sp"
app:autoSizeMinTextSize="6dp"
app:autoSizeTextType="uniform"
app:layout_constraintBaseline_toBaselineOf="@id/text_block_height" app:layout_constraintBaseline_toBaselineOf="@id/text_block_height"
app:layout_constraintEnd_toStartOf="@id/text_block_height_prefix" app:layout_constraintEnd_toStartOf="@id/text_block_height_prefix"
app:layout_constraintHorizontal_bias="0" app:layout_constraintHorizontal_bias="0"
@ -204,7 +206,8 @@
app:layout_constraintEnd_toEndOf="@id/top_box_icon_background" app:layout_constraintEnd_toEndOf="@id/top_box_icon_background"
app:layout_constraintHeight_percent="0.0408" app:layout_constraintHeight_percent="0.0408"
app:layout_constraintStart_toStartOf="@id/top_box_icon_background" app:layout_constraintStart_toStartOf="@id/top_box_icon_background"
app:layout_constraintTop_toTopOf="@id/top_box_icon_background" /> app:layout_constraintTop_toTopOf="@id/top_box_icon_background"
android:contentDescription="@string/content_description_transaction_details_direction" />
<TextView <TextView
android:id="@+id/top_box_label" android:id="@+id/top_box_label"
@ -294,7 +297,8 @@
app:layout_constraintEnd_toEndOf="@id/space_spots_memo" app:layout_constraintEnd_toEndOf="@id/space_spots_memo"
app:layout_constraintStart_toStartOf="@id/space_spots_memo" app:layout_constraintStart_toStartOf="@id/space_spots_memo"
app:layout_constraintTop_toTopOf="@id/subway_label_memo" app:layout_constraintTop_toTopOf="@id/subway_label_memo"
tools:visibility="visible" /> tools:visibility="visible"
android:contentDescription="@string/content_description_transaction_details_memo" />
<View <View
android:id="@+id/subway_spot_address" android:id="@+id/subway_spot_address"
@ -332,8 +336,9 @@
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:textColor="@color/tx_text_light_dimmed" android:textColor="@color/tx_text_light_dimmed"
android:textSize="18dp" android:textSize="18sp"
android:visibility="gone" android:visibility="gone"
tools:visibility="visible"
app:layout_constraintStart_toStartOf="@id/subway_line" app:layout_constraintStart_toStartOf="@id/subway_line"
app:layout_constraintTop_toTopOf="@id/subway_line" app:layout_constraintTop_toTopOf="@id/subway_line"
tools:text="+0.0001 network fee" /> tools:text="+0.0001 network fee" />
@ -346,7 +351,7 @@
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:textColor="@color/tx_text_light_dimmed" android:textColor="@color/tx_text_light_dimmed"
android:textSize="18dp" android:textSize="18sp"
android:visibility="gone" android:visibility="gone"
app:layout_constraintStart_toStartOf="@id/subway_line" app:layout_constraintStart_toStartOf="@id/subway_line"
app:layout_constraintTop_toBottomOf="@id/subway_label_fee" app:layout_constraintTop_toBottomOf="@id/subway_label_fee"
@ -359,8 +364,9 @@
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:paddingEnd="8dp" android:paddingEnd="8dp"
android:paddingStart="8dp"
android:text="@string/transaction_with_memo" android:text="@string/transaction_with_memo"
android:textSize="18dp" android:textSize="18sp"
android:visibility="invisible" android:visibility="invisible"
app:layout_constraintStart_toStartOf="@id/subway_line" app:layout_constraintStart_toStartOf="@id/subway_line"
app:layout_constraintTop_toBottomOf="@id/subway_label_source" app:layout_constraintTop_toBottomOf="@id/subway_label_source"
@ -378,7 +384,7 @@
android:maxLines="3" android:maxLines="3"
android:scrollbars="vertical" android:scrollbars="vertical"
android:textColor="@color/tx_text_light_dimmed" android:textColor="@color/tx_text_light_dimmed"
android:textSize="18dp" android:textSize="18sp"
android:visibility="gone" android:visibility="gone"
app:layout_constraintEnd_toEndOf="@id/guideline_keyline_end" app:layout_constraintEnd_toEndOf="@id/guideline_keyline_end"
app:layout_constraintStart_toStartOf="@id/subway_line" app:layout_constraintStart_toStartOf="@id/subway_line"
@ -394,7 +400,8 @@
android:visibility="gone" android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/spacer_memo_icon" app:layout_constraintBottom_toBottomOf="@id/spacer_memo_icon"
app:layout_constraintStart_toEndOf="@id/spacer_memo_icon" app:layout_constraintStart_toEndOf="@id/spacer_memo_icon"
app:layout_constraintTop_toTopOf="@id/spacer_memo_icon" /> app:layout_constraintTop_toTopOf="@id/spacer_memo_icon"
android:contentDescription="@string/content_description_transaction_details_memo_icon" />
<View <View
android:id="@+id/hit_area_memo_subway" android:id="@+id/hit_area_memo_subway"
@ -429,7 +436,7 @@
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:textColor="@color/text_light" android:textColor="@color/text_light"
android:textSize="18dp" android:textSize="18sp"
android:visibility="gone" android:visibility="gone"
app:layout_constraintStart_toStartOf="@id/subway_line" app:layout_constraintStart_toStartOf="@id/subway_line"
app:layout_constraintTop_toBottomOf="@id/subway_label_memo" app:layout_constraintTop_toBottomOf="@id/subway_label_memo"
@ -442,7 +449,7 @@
android:layout_marginBottom="24dp" android:layout_marginBottom="24dp"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:textColor="@color/tx_text_light_dimmed" android:textColor="@color/tx_text_light_dimmed"
android:textSize="18dp" android:textSize="18sp"
app:layout_constraintBottom_toTopOf="@id/bottom_box_background" app:layout_constraintBottom_toTopOf="@id/bottom_box_background"
app:layout_constraintStart_toStartOf="@id/subway_line" app:layout_constraintStart_toStartOf="@id/subway_line"
tools:text="confirmed" /> tools:text="confirmed" />
@ -477,7 +484,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="2dp" android:layout_marginTop="2dp"
android:text="Total Spent" android:text="@string/transaction_details_total"
android:textColor="@color/tx_primary" android:textColor="@color/tx_primary"
android:textSize="16sp" android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@id/bottom_box_value" app:layout_constraintBottom_toTopOf="@id/bottom_box_value"
@ -495,7 +502,7 @@
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
android:layout_marginTop="4dp" android:layout_marginTop="4dp"
android:maxLines="1" android:maxLines="1"
android:text="$4.32" tools:text="$4.32"
android:textAppearance="@style/Zcash.TextAppearance.Zec" android:textAppearance="@style/Zcash.TextAppearance.Zec"
android:textColor="@color/text_light" android:textColor="@color/text_light"
android:textSize="36sp" android:textSize="36sp"
@ -519,7 +526,7 @@
style="@style/Zcash.Button.OutlinedButton" style="@style/Zcash.Button.OutlinedButton"
android:gravity="center" android:gravity="center"
android:padding="12dp" android:padding="12dp"
android:text="Explore" android:text="@string/transaction_details_button_explore"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/tx_text_light_dimmed_less" android:textColor="@color/tx_text_light_dimmed_less"
app:icon="@drawable/ic_baseline_launch_24" app:icon="@drawable/ic_baseline_launch_24"
@ -542,6 +549,6 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:visibility="gone" android:visibility="gone"
app:constraint_referenced_ids="icon_memo, hit_area_memo_icon, spacer_memo_icon" /> app:constraint_referenced_ids="icon_memo, hit_area_memo_icon" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -27,26 +27,26 @@
android:layout_width="1dp" android:layout_width="1dp"
android:layout_height="0dp" android:layout_height="0dp"
android:background="@color/text_light_dimmed" android:background="@color/text_light_dimmed"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintBottom_toBottomOf="parent"/>
<View <View
android:id="@+id/divider_right" android:id="@+id/divider_right"
android:layout_width="1dp" android:layout_width="1dp"
android:layout_height="0dp" android:layout_height="0dp"
android:background="@color/text_light_dimmed" android:background="@color/text_light_dimmed"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintBottom_toBottomOf="parent"/>
<View <View
android:id="@+id/indicator" android:id="@+id/indicator"
android:layout_width="3dp" android:layout_width="3dp"
android:layout_height="60dp" android:layout_height="60dp"
android:layout_marginStart="16dp"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:background="@drawable/background_indicator_inbound" android:background="@drawable/background_indicator_inbound"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
@ -59,6 +59,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:contentDescription="@string/content_description_transaction_shield"
android:src="@drawable/ic_check_shield" android:src="@drawable/ic_check_shield"
app:layout_constraintBottom_toTopOf="@id/text_transaction_bottom" app:layout_constraintBottom_toTopOf="@id/text_transaction_bottom"
app:layout_constraintStart_toEndOf="@id/indicator" app:layout_constraintStart_toEndOf="@id/indicator"
@ -76,20 +77,21 @@
app:layout_constraintBottom_toTopOf="@id/text_transaction_bottom" app:layout_constraintBottom_toTopOf="@id/text_transaction_bottom"
app:layout_constraintStart_toEndOf="@id/indicator" app:layout_constraintStart_toEndOf="@id/indicator"
app:layout_constraintTop_toTopOf="@id/indicator" app:layout_constraintTop_toTopOf="@id/indicator"
app:layout_constraintVertical_chainStyle="packed" /> app:layout_constraintVertical_chainStyle="packed"
android:importantForAccessibility="no"/>
<TextView <TextView
android:id="@+id/text_transaction_top" android:id="@+id/text_transaction_top"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:maxLines="1"
android:textColor="@color/text_light" android:textColor="@color/text_light"
android:textSize="14sp" android:textSize="14sp"
android:maxLines="1" app:autoSizeTextType="uniform"
android:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="@id/image_shield_invisible" app:layout_constraintBottom_toBottomOf="@id/image_shield_invisible"
app:layout_constraintStart_toEndOf="@id/image_shield"
app:layout_constraintEnd_toStartOf="@id/text_transaction_amount" app:layout_constraintEnd_toStartOf="@id/text_transaction_amount"
app:layout_constraintStart_toEndOf="@id/image_shield"
app:layout_constraintTop_toTopOf="@id/image_shield_invisible" app:layout_constraintTop_toTopOf="@id/image_shield_invisible"
app:layout_constraintVertical_chainStyle="packed" app:layout_constraintVertical_chainStyle="packed"
app:layout_goneMarginStart="16dp" app:layout_goneMarginStart="16dp"
@ -104,26 +106,26 @@
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:paddingEnd="16dp" android:paddingEnd="16dp"
android:autoSizeTextType="uniform"
android:text="This is a very long sentence and it better not messup the UI"
android:textColor="@color/text_light_dimmed" android:textColor="@color/text_light_dimmed"
android:textSize="12sp" android:textSize="12sp"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="@id/indicator" app:layout_constraintBottom_toBottomOf="@id/indicator"
app:layout_constraintEnd_toStartOf="@id/text_transaction_amount" app:layout_constraintEnd_toStartOf="@id/text_transaction_amount"
app:layout_constraintStart_toEndOf="@id/indicator" app:layout_constraintStart_toEndOf="@id/indicator"
app:layout_constraintTop_toBottomOf="@id/image_shield_invisible" /> app:layout_constraintTop_toBottomOf="@id/image_shield_invisible"
tools:text="This is a very long sentence and it better not messup the UI" />
<TextView <TextView
android:id="@+id/text_transaction_amount" android:id="@+id/text_transaction_amount"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:paddingStart="12dp"
android:autoSizeTextType="uniform"
android:gravity="center_vertical" android:gravity="center_vertical"
android:maxLines="1" android:maxLines="1"
android:paddingStart="12dp"
android:textColor="@color/colorPrimary" android:textColor="@color/colorPrimary"
android:textSize="20sp" android:textSize="20sp"
app:autoSizeTextType="uniform"
app:layout_constraintBottom_toBottomOf="@id/indicator" app:layout_constraintBottom_toBottomOf="@id/indicator"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/indicator" app:layout_constraintTop_toTopOf="@id/indicator"

View File

@ -90,11 +90,6 @@
android:id="@+id/nav_send" android:id="@+id/nav_send"
android:name="cash.z.ecc.android.ui.send.SendFragment" android:name="cash.z.ecc.android.ui.send.SendFragment"
tools:layout="@layout/fragment_send" > tools:layout="@layout/fragment_send" >
<action
android:id="@+id/action_nav_send_to_send_final"
app:destination="@id/nav_send_final"
app:exitAnim="@anim/anim_exit_to_left"
app:enterAnim="@anim/anim_enter_from_right"/>
<action <action
android:id="@+id/action_nav_send_to_nav_scan" android:id="@+id/action_nav_send_to_nav_scan"
app:destination="@id/nav_scan" /> app:destination="@id/nav_scan" />
@ -156,6 +151,11 @@
app:destination="@id/nav_history" app:destination="@id/nav_history"
app:popUpTo="@id/nav_home" app:popUpTo="@id/nav_home"
app:popUpToInclusive="false"/> app:popUpToInclusive="false"/>
<action
android:id="@+id/action_nav_send_final_to_nav_send"
app:destination="@id/nav_send"
app:popUpTo="@id/nav_send"
app:popUpToInclusive="false"/>
</fragment> </fragment>

View File

@ -0,0 +1,16 @@
<resources>
<string name="custom_translation_decimal_key">,</string>
<string name="custom_translation_decimal_separator">,</string>
<string name="custom_translation_format_date_time_brief">d/M HH:mma</string>
<!-- Manual -->
<string name="custom_translation_verify">Verifica</string>
<string name="custom_translation_birthday">Fecha inicial: %1$,d</string>
<string name="custom_translation_expecting">Esperando</string>
<string name="custom_translation_syncing">Sincronizando…%1$d %%</string>
<string name="custom_translation_scanning">Escaneando…%1$d %%</string>
<string name="custom_translation_send_confirm">Confirma que quieres enviar</string>
<string name="custom_translation_chars">caracteres</string>
<string name="custom_translation_sending">Enviando</string>
</resources>

View File

@ -0,0 +1,74 @@
<resources>
<string name="translated_app_name">Billetera ECC</string>
<string name="translated_loadingtext">Cargando</string>
<string name="translated_welcometext">Bienvenido</string>
<string name="translated_fiattext">fiduciario</string>
<string name="translated_title_backupseed">Tu semilla de respaldo</string>
<string name="translated_copy_backupseed">Te recomendamos hace respaldarla en papel y obtener un administrador de contraseñas</string>
<string name="translated_feedback_walletbackupstatus">Tu Wallet necesita ser respaldada</string>
<string name="translated_seed_verify">Verifica tu frase de respaldo</string>
<string name="translated_seed_verifytext">Ingresa las palabras faltantes para verificar tu frase de respaldo</string>
<string name="translated_seed_restore">Restaurando desde frase de respaldo</string>
<string name="translated_seed_restoretext">Deberás ingresar todas las 24 palabras de tu frase semilla en orden</string>
<string name="translated_seed_reminder">¡Tus fondos están en riesgo!</string>
<string name="translated_seed_remindertext">Recuerda, en Zcash tu eres el banco. Cualquiera que tenga tu frase semilla podrá acceder a tu wallet</string>
<string name="translated_seed_remindertext2">Deberías respaldar tu wallet inmediatamente. Solo tú puedes hacerlo.</string>
<string name="translated_seed_accepted">¡Frase semilla aceptada! ¡Escaneando!</string>
<string name="translated_seed_birthdaytext">Estamos buscando transacciones de tu wallet en la blockchain. Si nos provees una fecha inicial de tu wallet podemos hacerlo más rápido.</string>
<string name="translated_seed_enterdate">Ingresar fecha</string>
<string name="translated_seed_birthday">Fecha inicial</string>
<string name="translated_button_dontknow">No lo sé</string>
<string name="translated_button_backup">Respaldo</string>
<string name="translated_button_skip">Omitir por ahora</string>
<string name="translated_balance_screen">Pantalla de Balance</string>
<string name="translated_balance_amounttosend">Ingresar un monto para enviar</string>
<string name="translated_balance_expecting">esperando XX ZEC</string>
<string name="translated_balance_available">Disponible</string>
<string name="translated_balance_nofunds">No hay fondos disponibles</string>
<string name="translated_balance_syncing">Sincronizando .... %</string>
<string name="translated_balance_scanning">Escaneando .... %</string>
<string name="translated_button_sendamount">Enviar Monto</string>
<string name="translated_button_wallethistory">Historial de Wallet</string>
<string name="translated_address_screen">Tu dirección</string>
<string name="translated_address_shielded">Tu dirección blindada</string>
<string name="translated_address_transparent">Tu dirección transparente</string>
<string name="translated_feedback_addresscopied">¡Dirección Copiada!</string>
<string name="translated_button_copytoclipboard">Copiar al portapapeles</string>
<string name="translated_profile_screen">Usuario Blindado</string>
<string name="translated_button_feedback">Enviar feedback</string>
<string name="translated_button_backup_wallet">Respaldar Wallet</string>
<string name="translated_button_applicationlogs">Ver Logs de Aplicación</string>
<string name="translated_button_close">Cerrar</string>
<string name="translated_send_screen">Pantalla de Envío</string>
<string name="translated_button_send">Enviar</string>
<string name="translated_send_fromshielded">de tu wallet blindada</string>
<string name="translated_send_fromtransparent">de tu wallet transparente</string>
<string name="translated_send_fromboth">desde ambas wallet</string>
<string name="translated_label_to">A</string>
<string name="translated_feedback_default">Ingresa una dirección Zcash Válida</string>
<string name="translated_feedback_shieldedaddress">Esta es una dirección blindada válida</string>
<string name="translated_feedback_transparentaddress">Esta es una dirección transparente válida</string>
<string name="translated_feedback_sameaddress">¡Cuidado, parece que esta es tu propia dirección!</string>
<string name="translated_feedback_invalidaddress">¡Cuidado, esta dirección no es válida!</string>
<string name="translated_label_memo">Memo</string>
<string name="translated_label_charactercount">caracteres</string>
<string name="translated_label_replyto">incluir remitente</string>
<string name="translated_send_onclipboard">En Portapapeles</string>
<string name="translated_send_lastused">Última Utilizada</string>
<string name="translated_send_unknown">Desconocido</string>
<string name="translated_send_scanqr">Escanear Dirección destinatario</string>
<string name="translated_send_securityauth">Identifícate para enviar</string>
<string name="translated_send_securityauth2">Confirma que quieres enviar XX ZEC a</string>
<string name="translated_send_sending">Enviando XX ZEC a</string>
<string name="translated_send_cancelled">Cancelado</string>
<string name="translated_send_sent">¡Enviado!</string>
<string name="translated_button_done">Listo</string>
<string name="translated_button_seedetails">Ver Detalles</string>
<string name="translated_button_back">Volver</string>
<string name="translated_screen_wallethistory">Historial de tu Wallet</string>
</resources>
<!-- ISSUES:
- button_backup isn't unique
-->

View File

@ -0,0 +1,16 @@
<resources>
<string name="custom_translation_decimal_key">,</string>
<string name="custom_translation_decimal_separator">,</string>
<string name="custom_translation_format_date_time_brief">d/M HH:mma</string>
<!-- Manual -->
<string name="custom_translation_verify">Controlla</string>
<string name="custom_translation_birthday">Birthday: %1$,d</string>
<string name="custom_translation_expecting">Aspettando</string>
<string name="custom_translation_syncing">Downloading…%1$d%%</string>
<string name="custom_translation_scanning">Scanning…%1$d%%</string>
<string name="custom_translation_send_confirm">Conferma che vuoi inviare</string>
<string name="custom_translation_chars">caratteri</string>
<string name="custom_translation_sending">Inviando</string>
</resources>

View File

@ -0,0 +1,63 @@
<resources>
<string name="translated_address_screen">Tuo Indirizzo</string>
<string name="translated_address_shielded">Il tuo indirizzo blindato</string>
<string name="translated_address_transparent">Il tuo indirizzo trasparente</string>
<string name="translated_balance_amounttosend">Inserisci un importo da inviare</string>
<string name="translated_balance_available">Disponibile</string>
<string name="translated_balance_expecting">aspettando %@ ZEC</string>
<string name="translated_balance_nofunds">Nessun fondo disponibile</string>
<string name="translated_balance_screen">Bilancio</string>
<string name="translated_balance_syncing">Sincronizzando %@%%</string>
<string name="translated_button_applicationlogs">Vedere Registri dell\'applicazione</string>
<string name="translated_button_back">ritornare</string>
<string name="translated_button_backup">Fare Backup</string>
<string name="translated_button_backup_wallet">Fare Backup</string>
<string name="translated_button_close">Chiudere</string>
<string name="translated_button_copytoclipboard">copia negli appunti</string>
<string name="translated_button_done">Pronto</string>
<string name="translated_button_dontknow">non lo so</string>
<string name="translated_button_feedback">"Invia feedback</string>
<string name="translated_button_seedetails">Vedi i dettagli</string>
<string name="translated_button_send">Invio</string>
<string name="translated_button_sendamount">Invia importo</string>
<string name="translated_button_skip">Salta per ora</string>
<string name="translated_button_wallethistory">cronologia del wallet</string>
<string name="translated_feedback_default">Inserisci un indirizzo Zcash valido</string>
<string name="translated_feedback_invalidaddress">Attenzione, questo indirizzo non è valido!</string>
<string name="translated_feedback_sameaddress">Attenzione, sembra che questo sia il tuo indirizzo!</string>
<string name="translated_feedback_shieldedaddress">Questo è un indirizzo blindato valido</string>
<string name="translated_feedback_transparentaddress">Questo è un indirizzo trasparente valido</string>
<string name="translated_feedback_walletbackupstatus">La tua Wallet ha bisogno di un backup</string>
<string name="translated_label_charactercount">caratteri</string>
<string name="translated_label_replyto">includere mittente</string>
<string name="translated_label_to">Per</string>
<string name="translated_loadingtext">Caricando</string>
<string name="translated_profile_screen">Utente blindato</string>
<string name="translated_screen_wallethistory">Storia del tuo Wallet</string>
<string name="translated_seed_accepted">Frase seme accettata!</string>
<string name="translated_seed_birthday">Blocco iniziale</string>
<string name="translated_seed_birthdaytext">Stiamo cercando transazioni dal tuo wallet sulla blockchain. Se ci fornisci una data iniziale del tuo Wallet, possiamo farlo più velocemente.</string>
<string name="translated_seed_enterdate">inserici data</string>
<string name="translated_seed_reminder">I tuoi fondi sono a rischio!</string>
<string name="translated_seed_remindertext">Ricorda, in Zcash sei tu la banca. Chiunque abbia la tua frase seme può accedere al tuo Wallet</string>
<string name="translated_seed_remindertext2">Dovresti eseguire immediatamente il backup del tuo portafoglio. Solo tu puoi farlo</string>
<string name="translated_seed_restore">recupero dalla frase di backup</string>
<string name="translated_seed_restoretext">Devi inserire tutte le 24 parole della tua frase seme in ordine</string>
<string name="translated_seed_verify">Controlla la tua frase di backup</string>
<string name="translated_seed_verifytext">Inserisci le parole mancanti per vedere la frase di backup</string>
<string name="translated_send_cancelled">Annullato</string>
<string name="translated_send_fromboth">dalle due wallet</string>
<string name="translated_send_fromshielded">del tuo wallet blindato</string>
<string name="translated_send_fromtransparent">del tuo wallet trasparente</string>
<string name="translated_send_lastused">Ultimo uso</string>
<string name="translated_send_onclipboard">Negli appunti</string>
<string name="translated_send_scanqr">Scansione indirizzo destinatario</string>
<string name="translated_send_screen">Invio</string>
<string name="translated_send_securityauth">Accedi per inviare</string>
<string name="translated_send_securityauth2">Conferma che vuoi inviare %@ ZEC a</string>
<string name="translated_send_sending">Inviando %@ ZEC a</string>
<string name="translated_send_sent">Inviato!</string>
<string name="translated_send_unknown">Sconosciuto</string>
<string name="translated_title_backupseed">Il tuo seme di backup</string>
<string name="translated_welcometext">Benvenuto</string>
</resources>

View File

@ -0,0 +1,16 @@
<resources>
<string name="custom_translation_decimal_key">,</string>
<string name="custom_translation_decimal_separator">,</string>
<string name="custom_translation_format_date_time_brief">d/M HH:mma</string>
<!-- Manual -->
<string name="custom_translation_verify"> 확인</string>
<string name="custom_translation_birthday">생일: %1$,d</string>
<string name="custom_translation_expecting">기대</string>
<string name="custom_translation_syncing">동기화…%1$d %%</string>
<string name="custom_translation_scanning">스캐닝…%1$d %%</string>
<string name="custom_translation_send_confirm">를 보낼 것인지 확인하십시오</string>
<string name="custom_translation_chars">문자</string>
<string name="custom_translation_sending">보내기</string>
</resources>

View File

@ -0,0 +1,74 @@
<resources>
<string name="translated_app_name">ECC 지갑</string>
<string name="translated_loadingtext">로딩</string>
<string name="translated_welcometext">환영</string>
<string name="translated_fiattext">₩ (currency = 통화)</string>
<string name="translated_title_backupseed">당신의 백업 시드</string>
<string name="translated_copy_backupseed">종이 백업과 비밀번호 보관소를 권장합니다.</string>
<string name="translated_feedback_walletbackupstatus">지갑을 백업해야합니다</string>
<string name="translated_seed_verify">백업 확인</string>
<string name="translated_seed_verifytext">누락 된 시드 단어를 입력하여 백업을 확인하십시오.</string>
<string name="translated_seed_restore">백업에서 복원</string>
<string name="translated_seed_restoretext">24 개의 시드 단어를 모두 순서대로 입력해야합니다.</string>
<string name="translated_seed_reminder">당신의 자금이 위험에 처해 있습니다</string>
<string name="translated_seed_remindertext">Zcash에서 당신은 은행이라는 것을 기억하십시오. 시드 문구가있는 사람은 누구나 지갑에 액세스 할 수 있습니다.</string>
<string name="translated_seed_remindertext2">다른 사람이 대신 백업 할 수 없으므로 즉시 백업해야합니다.</string>
<string name="translated_seed_accepted">시드 수락, 지금 스캔!</string>
<string name="translated_seed_birthdaytext">지갑에 대한 거래를 위해 블록 체인을 스캔하고 있습니다. 이 지갑의 생일 날짜를 제공 할 수 있다면 동기화 속도를 높일 수 있습니다.</string>
<string name="translated_seed_enterdate">날짜 입력</string>
<string name="translated_seed_birthday">생일</string>
<string name="translated_button_dontknow">몰라</string>
<string name="translated_button_backup">백업</string>
<string name="translated_button_skip">일단은 스킵</string>
<string name="translated_balance_screen">밸런스 화면</string>
<string name="translated_balance_amounttosend">보낼 금액 입력</string>
<string name="translated_balance_expecting">기대 XX ZEC</string>
<string name="translated_balance_available">사용 가능</string>
<string name="translated_balance_nofunds">사용할 수있는 자금 없습니다</string>
<string name="translated_balance_syncing">동기화</string>
<string name="translated_balance_scanning">스캐닝</string>
<string name="translated_button_sendamount">보내는 금액</string>
<string name="translated_button_wallethistory">지갑 내역</string>
<string name="translated_address_screen">당신의 주소</string>
<string name="translated_address_shielded">당신의 쉴드된 주소</string>
<string name="translated_address_transparent">당신의 투명 주소</string>
<string name="translated_feedback_addresscopied">주소 복사</string>
<string name="translated_button_copytoclipboard">클립 보드에 복사</string>
<string name="translated_profile_screen">쉴드된 사용자</string>
<string name="translated_button_feedback">피드백 보내기</string>
<string name="translated_button_backup_wallet">지갑 백업</string>
<string name="translated_button_applicationlogs">애플리케이션 로그보기</string>
<string name="translated_button_close">닫기</string>
<string name="translated_send_screen">보내는 화면</string>
<string name="translated_button_send">보내기</string>
<string name="translated_send_fromshielded">쉴드된 지갑에서</string>
<string name="translated_send_fromtransparent">투명한 지갑에서</string>
<string name="translated_send_fromboth">두 지갑에서</string>
<string name="translated_label_to"></string>
<string name="translated_feedback_default">유효한 zcash 주소를 입력하십시오</string>
<string name="translated_feedback_shieldedaddress">유효한 쉴드된 주소입니다.</string>
<string name="translated_feedback_transparentaddress">유효한 투명 주소입니다.</string>
<string name="translated_feedback_sameaddress">경고, 이것은 귀하의 주소 인 것 같습니다!</string>
<string name="translated_feedback_invalidaddress">경고,이 주소는 유효하지 않습니다!</string>
<string name="translated_label_memo">메모</string>
<string name="translated_label_charactercount">문자</string>
<string name="translated_label_replyto">회신 포함</string>
<string name="translated_send_onclipboard">클립 보드에서</string>
<string name="translated_send_lastused">마지막 사용</string>
<string name="translated_send_unknown">알 수 없는</string>
<string name="translated_send_scanqr">수신자 주소 스캔</string>
<string name="translated_send_securityauth">보내기 인증</string>
<string name="translated_send_securityauth2">XX ZEC를 보낼 것인지 확인하십시오.</string>
<string name="translated_send_sending">XX ZEC를 보내기</string>
<string name="translated_send_cancelled">취소되었습니다</string>
<string name="translated_send_sent">보냈습니다!</string>
<string name="translated_button_done"></string>
<string name="translated_button_seedetails">세부 사항보기</string>
<string name="translated_button_back">돌아가기</string>
<string name="translated_screen_wallethistory">지갑 내역</string>
</resources>
<!-- ISSUES:
- button_backup isn't unique
-->

View File

@ -0,0 +1,16 @@
<resources>
<string name="custom_translation_decimal_key">,</string>
<string name="custom_translation_decimal_separator">,</string>
<string name="custom_translation_format_date_time_brief">d/M HH:mma</string>
<!-- Manual -->
<string name="custom_translation_verify">Проверьте</string>
<string name="custom_translation_birthday">Создано: %1$,d</string>
<string name="custom_translation_expecting">Ожидание</string>
<string name="custom_translation_syncing">Синхронизация…%1$d%%</string>
<string name="custom_translation_scanning">Сканирование…%1$d%%</string>
<string name="custom_translation_send_confirm">Пожалуйста подтвердите, что хотите отправить</string>
<string name="custom_translation_chars">символы</string>
<string name="custom_translation_sending">Отправка</string>
</resources>

View File

@ -0,0 +1,69 @@
<resources>
<string name="translated_app_name">Кошелек ECC</string>
<string name="translated_loadingtext">Загрузка</string>
<string name="translated_welcometext">Добро пожаловать!</string>
<string name="translated_fiattext">В валюте</string>
<string name="translated_title_backupseed">Ваша фраза восстановления</string>
<string name="translated_copy_backupseed">Мы рекомендуем использовать резервную копию на бумаге и хранилище паролей.</string>
<string name="translated_feedback_walletbackupstatus">Необходимо создать резервную копию вашего кошелька.</string>
<string name="translated_seed_verify">Проверьте свою резервную копию.</string>
<string name="translated_seed_verifytext">Пожалуйста, заполните отсутствующие слова фразы восстановления, чтобы проверить свою резервную копию.</string>
<string name="translated_seed_restore">Восстановление из резервной копии</string>
<string name="translated_seed_restoretext">Вам необходимо будет ввести 24 слова фразы восстановления по порядку.</string>
<string name="translated_seed_reminder">Существует риск потерять средства!</string>
<string name="translated_seed_remindertext">Помните, что с Zcash Вы сами находитесь в роли банка. Любой, кто владеет фразой восстановления, получает полный доступ к кошельку.</string>
<string name="translated_seed_remindertext2">Вам следует незамедлительно создать резервную копию, так она позволит восстановить доступ к Вашему кошельку.</string>
<string name="translated_seed_accepted">Фраза восстановления принята, идёт сканирование блокчейна!</string>
<string name="translated_seed_birthdaytext">Мы сканируем блокчейн на предмет транзакций, которые относятся к вашему кошельку. Если вы можете указать дату создания данного кошелька, процесс ускорится.</string>
<string name="translated_seed_enterdate">Введите дату</string>
<string name="translated_seed_birthday">Кошелёк создан</string>
<string name="translated_button_dontknow">Я не знаю</string>
<string name="translated_button_backup">Резервная копия</string>
<string name="translated_button_skip">Пропустить</string>
<string name="translated_balance_screen">Экран баланса</string>
<string name="translated_balance_amounttosend">Введите сумму для отправки</string>
<string name="translated_balance_expecting">Ожидание XX ZEC</string>
<string name="translated_balance_available">Доступно</string>
<string name="translated_balance_nofunds">Нет доступных средств</string>
<string name="translated_balance_syncing">Синхронизация ... %</string>
<string name="translated_balance_scanning">Сканирование ... %</string>
<string name="translated_button_sendamount">Отправить сумму</string>
<string name="translated_button_wallethistory">История транзакций</string>
<string name="translated_address_screen">Ваш адрес</string>
<string name="translated_address_shielded">Ваш защищённый адрес</string>
<string name="translated_address_transparent">Ваш прозрачный адрес</string>
<string name="translated_feedback_addresscopied">Адрес скопирован!</string>
<string name="translated_button_copytoclipboard">Скопировать в буфер обмена</string>
<string name="translated_profile_screen">Защищенный пользователь</string>
<string name="translated_button_feedback">Отправить отзыв</string>
<string name="translated_button_backup_wallet">Резервная копия кошелька</string>
<string name="translated_button_applicationlogs">Просмотреть журнал операций приложения</string>
<string name="translated_button_close">Закрыть</string>
<string name="translated_send_screen">Отправить экран</string>
<string name="translated_button_send">Отправить</string>
<string name="translated_send_fromshielded">с Вашего кошелька</string>
<string name="translated_send_fromtransparent">с Вашего прозрачного кошелька</string>
<string name="translated_send_fromboth">с двух кошельков</string>
<string name="translated_label_to">на</string>
<string name="translated_feedback_default">Введите действующий Zcash-адрес </string>
<string name="translated_feedback_shieldedaddress">Это действующий защищённый адрес</string>
<string name="translated_feedback_transparentaddress">Это действующий прозрачный адрес</string>
<string name="translated_feedback_sameaddress">Внимание, похоже, это ваш адрес!</string>
<string name="translated_feedback_invalidaddress">Внимание, этот адрес недействителен!</string>
<string name="translated_label_memo">Заметка</string>
<string name="translated_label_charactercount">символы (символы)</string>
<string name="translated_label_replyto">добавить "ответ на"</string>
<string name="translated_send_onclipboard">В буфере обмена</string>
<string name="translated_send_lastused">Последний используемый</string>
<string name="translated_send_unknown">Неизвестно</string>
<string name="translated_send_scanqr">Сканировать адрес получателя</string>
<string name="translated_send_securityauth">Подтвердите, чтобы отправить</string>
<string name="translated_send_securityauth2">Пожалуйста подтвердите, что хотите отправить XX ZEC на адрес</string>
<string name="translated_send_sending">Отправка XX ZEC на</string>
<string name="translated_send_cancelled">Отменено</string>
<string name="translated_send_sent">Отправлено!</string>
<string name="translated_button_done">Готово</string>
<string name="translated_button_seedetails">Смотреть подробности</string>
<string name="translated_button_back">Вернуться назад</string>
<string name="translated_screen_wallethistory">История транзакций</string>
</resources>

View File

@ -0,0 +1,16 @@
<resources>
<string name="custom_translation_decimal_key">,</string>
<string name="custom_translation_decimal_separator">,</string>
<string name="custom_translation_format_date_time_brief">d/M HH:mma</string>
<!-- Manual -->
<string name="custom_translation_verify">验证</string>
<string name="custom_translation_birthday">创建日期: %1$,d</string>
<string name="custom_translation_expecting">预计为</string>
<string name="custom_translation_syncing">正在同步…%1$d%%</string>
<string name="custom_translation_scanning">正在扫描…%1$d%%</string>
<string name="custom_translation_send_confirm">请确认,您现在要发送</string>
<string name="custom_translation_chars">字符</string>
<string name="custom_translation_sending">正在发送</string>
</resources>

View File

@ -0,0 +1,69 @@
<resources>
<string name="translated_app_name">ECC 钱包</string>
<string name="translated_loadingtext">准备工作</string>
<string name="translated_welcometext">欢迎使用 ECC 钱包!</string>
<string name="translated_fiattext">法币</string>
<string name="translated_title_backupseed">您的备份种子(助记词)</string>
<string name="translated_copy_backupseed">我们建议您使用纸笔抄录备份种子,并锁到带密码的保险箱里。</string>
<string name="translated_feedback_walletbackupstatus">您的钱包需要备份。</string>
<string name="translated_seed_verify">需要验证您的备份。</string>
<string name="translated_seed_verifytext">请填入缺失的词语,以验证您的备份无误。</string>
<string name="translated_seed_restore">从备份助记词中恢复钱包</string>
<string name="translated_seed_restoretext">您需要按照顺序输入 24 个助记词。</string>
<string name="translated_seed_reminder">您的资金处在安全风险中!</string>
<string name="translated_seed_remindertext">请记住,使用 Zcash 的时候,没有其他人在托管您的资金,您 是自己的银行。使用资金的唯一凭证就是助记词。如有他人知道了您的助记词,就有了转移您资金的所有权限。</string>
<string name="translated_seed_remindertext2">请立即备份,如果您遗失了自己的备份,没有任何人能帮您恢复钱包、找回资金。</string>
<string name="translated_seed_accepted">已接受助记词,正在扫描您的帐户余额。</string>
<string name="translated_seed_birthdaytext">软件正在扫描区块链以发现与您的钱包有关的交易。如果您能提供这个钱包的创建时间,可以加快扫描速度。</string>
<string name="translated_seed_enterdate">输入日期</string>
<string name="translated_seed_birthday">创建日期</string>
<string name="translated_button_dontknow">我不知道 / 我忘了</string>
<string name="translated_button_backup">备份</string>
<string name="translated_button_skip">跳过</string>
<string name="translated_balance_screen">余额扫描</string>
<string name="translated_balance_amounttosend">输入您想发送的数额</string>
<string name="translated_balance_expecting">预计为 XX ZEC</string>
<string name="translated_balance_available">可用</string>
<string name="translated_balance_nofunds">暂无可用的款项</string>
<string name="translated_balance_syncing">正在同步 ...%</string>
<string name="translated_balance_scanning">正在扫描 ...%</string>
<string name="translated_button_sendamount">发送数额</string>
<string name="translated_button_wallethistory">钱包历史</string>
<string name="translated_address_screen">您的地址</string>
<string name="translated_address_shielded">您的隐蔽地址</string>
<string name="translated_address_transparent">您的透明地址</string>
<string name="translated_feedback_addresscopied">已复制地址</string>
<string name="translated_button_copytoclipboard">已复制到剪贴板</string>
<string name="translated_profile_screen">隐蔽用户</string>
<string name="translated_button_feedback">发送反馈</string>
<string name="translated_button_backup_wallet">备份钱包</string>
<string name="translated_button_applicationlogs">查看应用日志</string>
<string name="translated_button_close">关闭</string>
<string name="translated_send_screen">发送资金功能</string>
<string name="translated_button_send">发送</string>
<string name="translated_send_fromshielded">从您的隐蔽钱包发送</string>
<string name="translated_send_fromtransparent">从您的透明钱包发送</string>
<string name="translated_send_fromboth">同时从您的隐蔽钱包和透明钱包拼凑数额</string>
<string name="translated_label_to"></string>
<string name="translated_feedback_default">请输入一个有效的 Zcash 地址</string>
<string name="translated_feedback_shieldedaddress">这是一个有效的隐蔽地址</string>
<string name="translated_feedback_transparentaddress">这是一个有效的透明地址</string>
<string name="translated_feedback_sameaddress">警告,该地址似乎是您自己的地址!</string>
<string name="translated_feedback_invalidaddress">警告,该地址不是一个有效地址!</string>
<string name="translated_label_memo">备注</string>
<string name="translated_label_charactercount">字符</string>
<string name="translated_label_replyto"></string>
<string name="translated_send_onclipboard">在剪贴板内</string>
<string name="translated_send_lastused">上一次使用</string>
<string name="translated_send_unknown">未知</string>
<string name="translated_send_scanqr">扫描接收人地址</string>
<string name="translated_send_securityauth">授权发送</string>
<string name="translated_send_securityauth2">请确认,您现在要发送 XX ZEC 发送给 </string>
<string name="translated_send_sending">正在发送 XX ZEC 给</string>
<string name="translated_send_cancelled">已取消</string>
<string name="translated_send_sent">已发送!</string>
<string name="translated_button_done">完成</string>
<string name="translated_button_seedetails">查看细节</string>
<string name="translated_button_back">返回</string>
<string name="translated_screen_wallethistory">您的钱包历史</string>
</resources>

View File

@ -57,6 +57,9 @@
<!-- every color here should be a reference to a palette color <!-- every color here should be a reference to a palette color
but have a more useful name for use in code --> but have a more useful name for use in code -->
<color name="app_icon_background_0">#FF3F3F4F</color>
<color name="app_icon_background_1">#FF000000</color>
<color name="app_icon_foreground">#FFB900</color>
<color name="background_banner">@color/zcashBlack_dark</color> <color name="background_banner">@color/zcashBlack_dark</color>
<color name="background_banner_stroke">#282828</color> <color name="background_banner_stroke">#282828</color>
<color name="scan_overlay_background">@color/zcashBlack_87</color> <color name="scan_overlay_background">@color/zcashBlack_87</color>

View File

@ -0,0 +1,19 @@
<resources>
<string name="custom_translation_decimal_separator">.</string>
<string name="custom_translation_decimal_key">.</string>
<string name="custom_translation_format_date_time_brief">M/d h:mma</string>
<!-- Manual -->
<string name="custom_translation_verify">Verify</string>
<string name="custom_translation_birthday">Birthday Height: %1$,d</string>
<string name="custom_translation_expecting">Expecting</string>
<string name="custom_translation_syncing">Downloading…%1$d%%</string>
<string name="custom_translation_scanning">Scanning…%1$d%%</string>
<string name="custom_translation_send_confirm">Please confirm that you want to send</string>
<string name="custom_translation_from_your">"from your "</string>
<string name="custom_translation_wallet">" wallet"</string>
<string name="custom_translation_chars">chars</string>
<string name="custom_translation_sending">Sending</string>
</resources>

View File

@ -1,71 +1,266 @@
<resources> <resources xmlns:tools="http://schemas.android.com/tools">
<!-- Common --> <!-- Common -->
<string name="done">Done</string> <string name="done" tools:ignore="MissingTranslation">@string/translated_button_done</string>
<string name="cancel">Cancel</string> <string name="cancel">Cancel</string>
<string name="unknown">Unknown</string> <string name="unknown" tools:ignore="MissingTranslation">@string/translated_send_unknown</string>
<string name="blank"></string> <string name="blank"></string>
<!-- Home -->
<string name="home_history_button_text">View History</string>
<string name="home_title">Enter an amount to send</string>
<!-- Transaction details --> <!-- -->
<string name="transaction_title">Transaction Details</string> <!-- SCREENS -->
<string name="transaction_block_height_prefix">"from block "</string> <!-- -->
<string name="transaction_with_memo">with a memo</string>
<string name="transaction_prefix_from">reply-to</string>
<string name="transaction_prefix_to">to</string>
<!-- Send Flow --> <!-- Screen: Backup -->
<string name="send_hint_input_zcash_address">Enter a shielded Zcash address</string> <string name="backup_button_done" tools:ignore="MissingTranslation">@string/translated_button_done</string>
<string name="send_hint_input_zcash_amount">Enter an amount to send</string> <string name="backup_button_primary">@string/custom_translation_verify</string>
<string name="send_memo_excluded_message">Your transaction is shielded and your address is not available to the recipient</string> <string name="backup_format_birthday_height">@string/custom_translation_birthday</string>
<string name="send_memo_included_message">Your transaction is shielded but your address will be sent to the recipient via the memo</string> <string name="backup_instruction_store_words">Store these backup words securely.</string>
<string name="send_pending_button_text">Cancel</string> <string name="backup_slogan">empowering\neveryone\nwith\neconomic\nfreedom</string>
<string name="send_failed_button_text">Retry</string> <string name="backup_verification_not_implemented">Backup verification coming soon!</string>
<string name="send_complete_button_text">See Details</string>
<string name="send_banner_address_user">Your shielded address</string>
<string name="send_banner_address_unknown">@string/unknown</string>
<!-- Address --> <!-- Screen: Feedback -->
<string name="address_label_shielded">Your Shielded Address</string>
<string name="scan_address_title">Scan Recipient Address</string>
<string name="receive_address_title">Receive Funds</string>
<!-- Feedback -->
<string name="feedback_question_1">Any details you\'d like to share?</string>
<string name="feedback_question_2">Was your balance clear?</string>
<string name="feedback_question_3">What feature would you like to see next?</string>
<string name="feedback_hint_1">My experience was . . .</string> <string name="feedback_hint_1">My experience was . . .</string>
<string name="feedback_hint_2">My balance was . . .</string> <string name="feedback_hint_2">My balance was . . .</string>
<string name="feedback_hint_3">I\'d like . . .</string> <string name="feedback_hint_3">I\'d like . . .</string>
<string name="feedback_question_1">Any details you\'d like to share?</string>
<string name="feedback_question_2">Was your balance clear?</string>
<string name="feedback_question_3">What feature would you like to see next?</string>
<string name="feedback_thanks">Thanks for the feedback!</string>
<!-- Profile --> <!-- Screen: History -->
<string name="profile_shielded_user">Shielded User</string> <string name="history_address_label">Shielded address:</string>
<string name="profile_send_feedback">Send Feedback</string> <string name="history_balance_available" tools:ignore="MissingTranslation">@string/translated_balance_available</string>
<string name="profile_backup_wallet">Backup Wallet</string> <string name="history_balance_updating">Updating</string>
<string name="profile_see_application_logs">See Application Logs</string> <string name="history_empty_text">No history yet.</string>
<string name="profile_ecc_wallet">ECC Wallet</string> <string name="history_header_transactions" tools:ignore="MissingTranslation">@string/translated_screen_wallethistory</string>
<string name="history_instruction_enter_amount" tools:ignore="MissingTranslation">@string/translated_balance_amounttosend</string>
<!-- Screen: Home -->
<string name="home_balance_available" tools:ignore="MissingTranslation">@string/translated_balance_available</string>
<string name="home_balance_updating">Updating</string>
<string name="home_banner_expecting">@string/custom_translation_expecting</string>
<string name="home_button_send_disconnected">Reconnecting . . .</string>
<string name="home_button_send_downloading">@string/custom_translation_syncing</string>
<string name="home_button_send_has_funds" tools:ignore="MissingTranslation">@string/translated_button_sendamount</string>
<string name="home_button_send_idle">IDLE</string>
<string name="home_button_send_no_funds" tools:ignore="MissingTranslation">@string/translated_balance_nofunds</string>
<string name="home_button_send_scanning">@string/custom_translation_scanning</string>
<string name="home_button_send_updating">Updating</string>
<string name="home_button_send_validating">Validating . . .</string>
<string name="home_dialog_no_balance_button_positive">View Address</string>
<string name="home_dialog_no_balance_message">To make full use of this wallet, deposit funds to your address.</string>
<string name="home_dialog_no_balance_title" tools:ignore="MissingTranslation">@string/translated_balance_nofunds</string>
<string name="home_history_button_text" tools:ignore="MissingTranslation">@string/translated_button_wallethistory</string>
<string name="home_instruction_enter_amount" tools:ignore="MissingTranslation">@string/translated_balance_amounttosend</string>
<string name="home_instruction_fund_now">Fund Now</string>
<string name="home_no_balance">@string/translated_balance_nofunds</string>
<string name="home_title" tools:ignore="MissingTranslation">@string/translated_balance_amounttosend</string>
<!-- Screen: Landing -->
<string name="landing_backup_skipped_message_1">Are you sure? Without a backup, funds can be lost FOREVER!</string>
<string name="landing_backup_skipped_message_2">You can\'t backup later. You\'re probably going to lose your funds!</string>
<string name="landing_button_backup_skipped_1">Later</string>
<string name="landing_button_backup_skipped_2">I\'ve been warned</string>
<string name="landing_button_primary">New</string>
<string name="landing_button_primary_create_success" tools:ignore="MissingTranslation">@string/translated_button_backup</string>
<string name="landing_button_progress_create">Creating</string>
<string name="landing_button_secondary">Restore</string>
<string name="landing_button_secondary_create_success" tools:ignore="MissingTranslation">@string/translated_button_skip</string>
<string name="landing_button_secondary_import_success" tools:ignore="MissingTranslation">@string/translated_button_skip</string>
<string name="landing_create_success_message">Wallet created! Congratulations!</string>
<string name="landing_import_success_message">Wallet imported! Congratulations!</string>
<string name="landing_import_success_primary_button" tools:ignore="MissingTranslation">@string/translated_button_backup</string>
<string name="landing_title">Welcome to the ECC Wallet!</string>
<!-- Screen: Profile -->
<string name="profile_app_version">v1.0.0-alpha05</string> <string name="profile_app_version">v1.0.0-alpha05</string>
<string name="profile_backup_wallet" tools:ignore="MissingTranslation">@string/translated_button_backup_wallet</string>
<string name="profile_ecc_wallet" tools:ignore="MissingTranslation">@string/translated_app_name</string>
<string name="profile_see_application_logs" tools:ignore="MissingTranslation">@string/translated_button_applicationlogs</string>
<string name="profile_send_feedback" tools:ignore="MissingTranslation">@string/translated_button_feedback</string>
<string name="profile_share_log_title">Share Log Files</string>
<string name="profile_shielded_user" tools:ignore="MissingTranslation">@string/translated_profile_screen</string>
<!-- Settings--> <!-- Screen: Receive -->
<string name="receive_address_label_shielded" tools:ignore="MissingTranslation">@string/translated_address_shielded</string>
<string name="receive_address_title">Receive Funds</string>
<string name="scan_address_title" tools:ignore="MissingTranslation">@string/translated_send_scanqr</string>
<!-- Screen: Scan -->
<string name="scan_invalid_address">"Invalid Zcash %1$s address:\n%2$s"</string>
<!-- Screen: Send Flow -->
<string name="send_address_hint" tools:ignore="MissingTranslation">@string/translated_label_to</string>
<string name="send_banner_address_unknown" tools:ignore="MissingTranslation">@string/translated_send_unknown</string>
<string name="send_banner_address_user" tools:ignore="MissingTranslation">@string/translated_address_shielded</string>
<string name="send_button_primary" tools:ignore="MissingTranslation">@string/translated_button_send</string>
<string name="send_checkbox_include_address" tools:ignore="MissingTranslation">@string/translated_label_replyto</string>
<string name="send_complete_button_text" tools:ignore="MissingTranslation">@string/translated_button_seedetails</string>
<string name="send_confirmation_prompt">@string/custom_translation_send_confirm</string>
<string name="send_failed_button_text">Retry</string>
<string name="send_final_button_primary_back" tools:ignore="MissingTranslation">@string/translated_button_back</string>
<string name="send_final_button_primary_cancel">Cancel</string>
<string name="send_final_button_primary_details" tools:ignore="MissingTranslation">@string/translated_button_seedetails</string>
<string name="send_final_button_primary_failed">Failed.</string>
<string name="send_final_button_primary_retry">Retry</string>
<string name="send_final_button_primary_sent" tools:ignore="MissingTranslation">@string/translated_send_sent</string>
<string name="send_final_error_encoding">The transaction could not be encoded.</string>
<string name="send_final_error_submitting">Unable to submit transaction to the network.</string>
<string name="send_final_result_cancelled" tools:ignore="MissingTranslation">@string/translated_send_cancelled</string>
<string name="send_final_sending">@string/custom_translation_sending</string>
<string name="send_final_to" tools:ignore="MissingTranslation">@string/translated_label_to</string>
<string name="send_fund_source_highlight">shielded</string>
<string name="send_fund_source_prefix">@string/custom_translation_from_your</string>
<string name="send_fund_source_suffix">@string/custom_translation_wallet</string>
<string name="send_hint_input_zcash_address">Enter a shielded Zcash address</string>
<string name="send_hint_input_zcash_amount" tools:ignore="MissingTranslation">@string/translated_balance_amounttosend</string>
<string name="send_history_clipboard" tools:ignore="MissingTranslation">@string/translated_send_onclipboard</string>
<string name="send_history_last" tools:ignore="MissingTranslation">@string/translated_send_lastused</string>
<string name="send_history_last_and_clipboard">"Last Used and On Clipboard"</string>
<string name="send_memo_chars_abbreviation">@string/custom_translation_chars</string>
<string name="send_memo_excluded_message">Your transaction is shielded and your address is not available to the recipient</string>
<string name="send_memo_hint">Memo</string>
<string name="send_memo_included_message">Your transaction is shielded but your address will be sent to the recipient via the memo</string>
<string name="send_pending_button_text">Cancel</string>
<string name="send_validation_address_invalid">This address appears to be invalid</string>
<string name="send_validation_address_self" tools:ignore="MissingTranslation">@string/translated_feedback_sameaddress</string>
<string name="send_validation_address_valid_taddr" tools:ignore="MissingTranslation">@string/translated_feedback_transparentaddress</string>
<string name="send_validation_address_valid_zaddr" tools:ignore="MissingTranslation">@string/translated_feedback_shieldedaddress</string>
<string name="send_validation_error_address_invalid" tools:ignore="MissingTranslation">@string/translated_feedback_default</string>
<string name="send_validation_error_amount_minimum">Please go back and enter at least 1 Zatoshi.</string>
<string name="send_validation_error_dust">Insufficient funds to cover miner\'s fee.</string>
<string name="send_validation_error_memo_length">Memo must be less than %1$d in length.</string>
<string name="send_validation_error_no_available_funds" tools:ignore="MissingTranslation">@string/translated_balance_nofunds</string>
<string name="send_validation_error_too_much">Please go back and enter no more than %1$s ZEC.</string>
<string name="send_validation_error_unknown_funds">Available funds not found. Please try again in a moment.</string>
<!-- Screen: Settings-->
<string name="settings_buttons_restore">Restore Defaults</string>
<string name="settings_change_lightwalletd_server">Change Lightwalletd Server</string> <string name="settings_change_lightwalletd_server">Change Lightwalletd Server</string>
<string name="settings_host_helper_text">Enter a valid host name or IP address</string>
<string name="settings_port_helper_text">Enter a valid port number</string>
<string name="settings_reset">Reset</string>
<string name="settings_server_address">Host</string> <string name="settings_server_address">Host</string>
<string name="settings_server_port">Port</string> <string name="settings_server_port">Port</string>
<string name="settings_toast_change_server_failure">Failed to change server!</string>
<string name="settings_toast_change_server_success">Successfully changed server!</string>
<string name="settings_update">Update</string> <string name="settings_update">Update</string>
<string name="settings_reset">Reset</string>
<string name="settings_port_helper_text">Enter a valid port number</string>
<string name="settings_host_helper_text">Enter a valid host name or IP address</string>
<!-- Screen: Transaction details -->
<string name="transaction_address_paid_you">paid you</string>
<string name="transaction_address_you_paid">You paid</string>
<string name="transaction_block_height_prefix">"from block "</string>
<string name="transaction_confirmation_count_unavailable">Confirmation count temporarily unavailable</string>
<string name="transaction_details_button_explore">Explore</string>
<string name="transaction_details_total">Total Spent</string>
<string name="transaction_instruction_tap">tap to view</string>
<string name="transaction_prefix_from">reply-to</string>
<string name="transaction_prefix_to" tools:ignore="MissingTranslation">@string/translated_label_to</string>
<string name="transaction_received">Received</string>
<string name="transaction_status_confirmed">Confirmed</string>
<string name="transaction_status_confirming">of 10 confirmations</string>
<string name="transaction_status_expired">Expired</string>
<string name="transaction_status_pending">Pending confirmation</string>
<string name="transaction_status_sent">Sent</string>
<string name="transaction_story_from_shielded" tools:ignore="MissingTranslation">@string/translated_send_fromshielded</string>
<string name="transaction_story_inbound">You Received</string>
<string name="transaction_story_inbound_total">Total Received</string>
<string name="transaction_story_network_fee">+%1$s network fee</string>
<string name="transaction_story_outbound">You Sent</string>
<string name="transaction_story_outbound_total">Total Sent</string>
<string name="transaction_story_to_shielded">to your shielded wallet</string>
<string name="transaction_timestamp_unavailable">Details Unavailable</string>
<string name="transaction_title">Transaction Details</string>
<string name="transaction_with_memo">with a memo</string>
<!-- -->
<!-- Content Descriptions -->
<!-- -->
<string name="content_description_backup_zcash_logo">Zcash Logo</string>
<string name="content_description_history_back">back</string>
<string name="content_description_history_copy">copy</string>
<string name="content_description_home_icon_profile">Profile</string>
<string name="content_description_home_icon_scan">Scan</string>
<string name="content_description_profile_back">back</string>
<string name="content_description_profile_copy">copy</string>
<string name="content_description_profile_settings">settings</string>
<string name="content_description_profile_zebra">zebra</string>
<string name="content_description_receive_back" tools:ignore="MissingTranslation">@string/translated_button_back</string>
<string name="content_description_receive_qr_background">QR background</string>
<string name="content_description_receive_qr_code">QR code</string>
<string name="content_description_receive_qr_logo">QR logo</string>
<string name="content_description_scan_back">back</string>
<string name="content_description_scan_frame">scan frame</string>
<string name="content_description_send_arrow">down arrow</string>
<string name="content_description_send_back">back</string>
<string name="content_description_send_scan_qr">scan QR code</string>
<string name="content_description_send_selected">selected</string>
<string name="content_description_send_shield">shield</string>
<string name="content_description_send_final_back">back</string>
<string name="content_description_settings_back">back</string>
<string name="content_description_transaction_shield">Shield</string>
<string name="content_description_transaction_details_back">back</string>
<string name="content_description_transaction_details_direction">direction</string>
<string name="content_description_transaction_details_memo">memo</string>
<string name="content_description_transaction_details_memo_icon">memo icon</string>
<!-- -->
<!-- Dialogs --> <!-- Dialogs -->
<string name="dialog_not_again">Don\'t show me again</string> <!-- -->
<string name="dialog_first_use_view_tx_title">Potential Privacy Risk</string>
<string name="dialog_first_use_view_tx_positive">View Tx</string>
<string name="dialog_first_use_view_tx_negative">Cancel</string>
<string name="dialog_first_use_view_tx_message">Are you sure you\'d like to leave the app? This could reduce privacy, if you do not trust the destination.</string>
<string name="dialog_error_change_server_button_positive">Ok</string>
<string name="dialog_error_change_server_title">Failed to Change Server</string>
<string name="dialog_error_critical_processing_button_negative">Exit</string>
<string name="dialog_error_critical_processing_button_positive">Retry</string>
<string name="dialog_error_critical_processing_message">Critical error while processing blocks!</string>
<string name="dialog_error_critical_processing_title">Processor Error</string>
<string name="dialog_error_invalid_seed_phrase_button_positive">Retry</string>
<string name="dialog_error_invalid_seed_phrase_message">That seed phrase appears to be invalid! Please double-check it and try again.\n\n%1$s</string>
<string name="dialog_error_invalid_seed_phrase_title">Oops! Invalid Seed Phrase</string>
<string name="dialog_error_processor_critical_button_negative">Exit</string>
<string name="dialog_error_processor_critical_button_positive">Retry</string>
<string name="dialog_error_processor_critical_message">Critical error while processing blocks!</string>
<string name="dialog_error_processor_critical_title">Processor Error</string>
<string name="dialog_error_scan_button_negative">Ignore</string>
<string name="dialog_error_scan_button_positive">Retry</string>
<string name="dialog_error_scan_failure_button_negative">Ignore</string>
<string name="dialog_error_scan_failure_button_positive">Retry</string>
<string name="dialog_error_scan_failure_title">Scan Failure</string>
<string name="dialog_error_scan_title">Scan Failure</string>
<string name="dialog_error_uninitialized_button_negative">Clear Data</string>
<string name="dialog_error_uninitialized_button_positive">Exit</string>
<string name="dialog_error_uninitialized_message">This wallet has not been initialized correctly! Perhaps an error occurred during install.\n\nThis can be fixed with a reset. First, locate your backup seed phrase, then CLEAR DATA and reimport it.</string>
<string name="dialog_error_uninitialized_title">Wallet Improperly Initialized</string>
<string name="dialog_first_use_view_tx_message">Are you sure you\'d like to leave the app? This could reduce privacy, if you do not trust the destination.</string>
<string name="dialog_first_use_view_tx_negative">Cancel</string>
<string name="dialog_first_use_view_tx_positive">View Tx</string>
<string name="dialog_first_use_view_tx_title">Potential Privacy Risk</string>
<string name="dialog_modify_server_button_negative">Cancel</string>
<string name="dialog_modify_server_button_positive">Update</string>
<string name="dialog_modify_server_message">WARNING: Entering an invalid or untrusted server might result in misconfiguration or loss of funds!</string>
<string name="dialog_modify_server_title">Modify Lightwalletd Server?</string>
<string name="dialog_not_again">Don\'t show me again</string>
<string name="dialog_nuke_wallet_button_negative">Erase Wallet</string>
<string name="dialog_nuke_wallet_button_positive">Cancel</string>
<string name="dialog_nuke_wallet_message">WARNING: Potential Loss of Funds\n\nClearing all wallet data and can result in a loss of funds, if you cannot locate your correct seed phrase.\n\nPlease confirm that you have your 24-word seed phrase available before proceeding.</string>
<string name="dialog_nuke_wallet_title">Nuke Wallet?</string>
<!-- -->
<!-- Misc --> <!-- Misc -->
<string name="app_name">ECC Wallet</string> <!-- -->
<string name="app_name" tools:ignore="MissingTranslation">@string/translated_app_name</string>
<string name="biometric_prompt_title" tools:ignore="MissingTranslation">@string/translated_send_securityauth</string>
<string name="camera_permission_denied">Well, this is awkward. You denied permission for the camera.</string>
<string name="error_launch_url">Failed to open browser.</string>
<string name="key_backspace"><![CDATA[<]]></string>
<string name="key_decimal">.</string>
<!-- <string name="key_decimal">@string/custom_translation_decimal_key</string>-->
<string name="mixpanel_project">a178e1ef062133fc121079cb12fa43c7</string> <string name="mixpanel_project">a178e1ef062133fc121079cb12fa43c7</string>
<string name="format_date_time">@string/custom_translation_format_date_time_brief</string>
</resources> </resources>

View File

@ -0,0 +1,74 @@
<resources>
<string name="translated_app_name">ECC Wallet</string>
<string name="translated_loadingtext">Loading</string>
<string name="translated_welcometext">Welcome</string>
<string name="translated_fiattext">Fiat</string>
<string name="translated_title_backupseed">Your backup seed</string>
<string name="translated_copy_backupseed">We recommend a paper backup and a password vault.</string>
<string name="translated_feedback_walletbackupstatus">Your wallet needs to be backed up.</string>
<string name="translated_seed_verify">Verify your backup.</string>
<string name="translated_seed_verifytext">Please fill out the missing seed words to verify your backup.</string>
<string name="translated_seed_restore">Restoring from a Backup</string>
<string name="translated_seed_restoretext">You will need to enter all 24 seed words in order.</string>
<string name="translated_seed_reminder">Your funds are at risk!</string>
<string name="translated_seed_remindertext">Remember, with Zcash YOU are the bank. Anyone with your seed phrase has access to your wallet.</string>
<string name="translated_seed_remindertext2">You should back this up immediately as no one else can restore it for you.</string>
<string name="translated_seed_accepted">Seed accepted, scanning!</string>
<string name="translated_seed_birthdaytext">We are scanning the blockchain for transactions pertaining to your wallet. If you can provide a birthday date for this wallet we can speed it up.</string>
<string name="translated_seed_enterdate">Enter Date</string>
<string name="translated_seed_birthday">Birthday</string>
<string name="translated_button_dontknow">I don\'t know</string>
<string name="translated_button_backup">Backup</string>
<string name="translated_button_skip">Skip for now</string>
<string name="translated_balance_screen">Balance Screen</string>
<string name="translated_balance_amounttosend">Enter an amount to send</string>
<string name="translated_balance_expecting">expecting XX ZEC</string>
<string name="translated_balance_available">Available</string>
<string name="translated_balance_nofunds">No funds available</string>
<string name="translated_balance_syncing">Syncing … %</string>
<string name="translated_balance_scanning">Scanning … %</string>
<string name="translated_button_sendamount">Send Amount</string>
<string name="translated_button_wallethistory">Wallet History</string>
<string name="translated_address_screen">Your Address</string>
<string name="translated_address_shielded">Your Shielded Address</string>
<string name="translated_address_transparent">Your Transparent Address</string>
<string name="translated_feedback_addresscopied">Address Copied!</string>
<string name="translated_button_copytoclipboard">Copy to clipboard</string>
<string name="translated_profile_screen">Shielded User</string>
<string name="translated_button_feedback">Send Feedback</string>
<string name="translated_button_backup_wallet">Backup Wallet</string>
<string name="translated_button_applicationlogs">See Application Logs</string>
<string name="translated_button_close">Close</string>
<string name="translated_send_screen">Send Screen</string>
<string name="translated_button_send">Send</string>
<string name="translated_send_fromshielded">from your shielded wallet</string>
<string name="translated_send_fromtransparent">from your transparent wallet</string>
<string name="translated_send_fromboth">from both wallets</string>
<string name="translated_label_to">To</string>
<string name="translated_feedback_default">Enter a valid Zcash address</string>
<string name="translated_feedback_shieldedaddress">This is a valid shielded address</string>
<string name="translated_feedback_transparentaddress">This is a valid transparent address</string>
<string name="translated_feedback_sameaddress">Warning, this appears to be your address!</string>
<string name="translated_feedback_invalidaddress">Warning, this address is not valid!</string>
<string name="translated_label_memo">Memo</string>
<string name="translated_label_charactercount">chars (characters)</string>
<string name="translated_label_replyto">include reply-to</string>
<string name="translated_send_onclipboard">On Clipboard</string>
<string name="translated_send_lastused">Last Used</string>
<string name="translated_send_unknown">Unknown</string>
<string name="translated_send_scanqr">Scan Recipient Address</string>
<string name="translated_send_securityauth">Authenticate to send</string>
<string name="translated_send_securityauth2">Please confirm you want to send XX ZEC to</string>
<string name="translated_send_sending">Sending XX ZEC to</string>
<string name="translated_send_cancelled">Cancelled</string>
<string name="translated_send_sent">Sent!</string>
<string name="translated_button_done">Done</string>
<string name="translated_button_seedetails">See Details</string>
<string name="translated_button_back">Go Back</string>
<string name="translated_screen_wallethistory">Your Wallet History</string>
</resources>
<!-- ISSUES:
- button_backup isn't unique
-->

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="app_icon_background_0">#FFB900</color>
<color name="app_icon_background_1">#664B00</color>
<color name="app_icon_foreground">#FF000000</color>
</resources>

View File

@ -0,0 +1,3 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="app_name" tools:ignore="MissingTranslation">ECC (QA)</string>
</resources>

View File

@ -1,11 +1,10 @@
package cash.z.ecc.android package cash.z.ecc.android
import cash.z.ecc.android.feedback.Feedback import cash.z.ecc.android.feedback.Feedback
import cash.z.ecc.android.sdk.db.entity.PendingTransaction
import cash.z.ecc.android.sdk.db.entity.*
import cash.z.ecc.android.ui.send.SendViewModel import cash.z.ecc.android.ui.send.SendViewModel
import cash.z.ecc.android.sdk.entity.* import com.nhaarman.mockitokotlin2.*
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyZeroInteractions
import com.nhaarman.mockitokotlin2.whenever
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.newSingleThreadContext import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.test.setMain import kotlinx.coroutines.test.setMain
@ -54,7 +53,7 @@ class SendViewModelTest {
@Test @Test
fun testUpdateMetrics_creating() { fun testUpdateMetrics_creating() {
// doNothing().whenever(sendViewModel).report(any()) doNothing().whenever(sendViewModel).report(any())
assertEquals(true, creatingTx.isCreating()) assertEquals(true, creatingTx.isCreating())
sendViewModel.updateMetrics(creatingTx) sendViewModel.updateMetrics(creatingTx)

View File

@ -9,6 +9,10 @@ object Deps {
const val buildToolsVersion = "29.0.2" const val buildToolsVersion = "29.0.2"
const val minSdkVersion = 21 const val minSdkVersion = 21
const val targetSdkVersion = 29 const val targetSdkVersion = 29
const val versionName = "1.0.0-alpha37"
const val versionCode = 1_00_00_037 // last digits are alpha(0XX) beta(2XX) rc(4XX) release(8XX). Ex: 1_08_04_401 is an release candidate build of version 1.8.4 and 1_08_04_800 would be the final release.
const val packageName = "cash.z.ecc.android"
object AndroidX { object AndroidX {
const val ANNOTATION = "androidx.annotation:annotation:1.1.0" const val ANNOTATION = "androidx.annotation:annotation:1.1.0"
@ -34,7 +38,7 @@ object Deps {
val LIFECYCLE_RUNTIME_KTX = "androidx.lifecycle:lifecycle-runtime-ktx:$version" val LIFECYCLE_RUNTIME_KTX = "androidx.lifecycle:lifecycle-runtime-ktx:$version"
val LIFECYCLE_EXTENSIONS = "androidx.lifecycle:lifecycle-extensions:$version" val LIFECYCLE_EXTENSIONS = "androidx.lifecycle:lifecycle-extensions:$version"
} }
object Navigation : Version("2.2.0") { object Navigation : Version("2.3.0") {
val FRAGMENT_KTX = "androidx.navigation:navigation-fragment-ktx:$version" val FRAGMENT_KTX = "androidx.navigation:navigation-fragment-ktx:$version"
val UI_KTX = "androidx.navigation:navigation-ui-ktx:$version" val UI_KTX = "androidx.navigation:navigation-ui-ktx:$version"
} }
@ -80,25 +84,26 @@ object Deps {
} }
object Zcash { object Zcash {
const val ANDROID_WALLET_PLUGINS = "cash.z.ecc.android:zcash-android-wallet-plugins:1.0.0" const val ANDROID_WALLET_PLUGINS = "cash.z.ecc.android:zcash-android-wallet-plugins:1.0.0"
const val KOTLIN_BIP39 = "cash.z.ecc.android:kotlin-bip39:1.0.0-beta09" const val KOTLIN_BIP39 = "cash.z.ecc.android:kotlin-bip39:1.0.1"
object Sdk : Version("1.1.0-beta07") { object Sdk : Version("1.1.0-beta08") {
val MAINNET = "cash.z.ecc.android:zcash-android-sdk-mainnet:$version" val MAINNET = "cash.z.ecc.android:zcash-android-sdk-mainnet:$version"
val TESTNET = "cash.z.ecc.android:sdk-testnet:$version" val TESTNET = "cash.z.ecc.android:zcash-android-sdk-testnet:$version"
} }
} }
object Misc { object Misc {
const val LOTTIE = "com.airbnb.android:lottie:3.1.0" const val LOTTIE = "com.airbnb.android:lottie:3.1.0"
const val CHIPS = "com.github.gmale:chips-input-layout:2.3.1" const val CHIPS = "com.github.gmale:chips-input-layout:2.3.1"
object Plugins { object Plugins {
const val SECURE_STORAGE = "de.adorsys.android:securestoragelibrary:1.2.2" const val SECURE_STORAGE = "com.github.gmale:secure-storage-android:0.0.3"//"de.adorsys.android:securestoragelibrary:1.2.2"
const val QR_SCANNER = "com.google.zxing:core:3.2.1" const val QR_SCANNER = "com.google.zxing:core:3.2.1"
} }
} }
object Test { object Test {
const val JUNIT = "junit:junit:4.12" const val JUNIT = "junit:junit:4.12"
const val MOKITO = "junit:junit:4.12" const val MOKITO = "org.mockito:mockito-android:3.5.10"
const val COROUTINES_TEST = "junit:junit:4.12" const val MOKITO_KOTLIN = "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0"
const val COROUTINES_TEST = "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.9"
object Android { object Android {
const val JUNIT = "androidx.test.ext:junit:1.1.1" const val JUNIT = "androidx.test.ext:junit:1.1.1"
const val ESPRESSO = "androidx.test.espresso:espresso-core:3.2.0" const val ESPRESSO = "androidx.test.espresso:espresso-core:3.2.0"

View File

@ -1,6 +1,7 @@
package cash.z.ecc.android.lockbox package cash.z.ecc.android.lockbox
import android.content.Context import android.content.Context
import android.provider.Settings
import cash.z.android.plugin.LockBoxPlugin import cash.z.android.plugin.LockBoxPlugin
import de.adorsys.android.securestoragelibrary.SecurePreferences import de.adorsys.android.securestoragelibrary.SecurePreferences
import java.nio.ByteBuffer import java.nio.ByteBuffer
@ -11,60 +12,72 @@ import javax.inject.Inject
class LockBox @Inject constructor(private val appContext: Context) : LockBoxPlugin { class LockBox @Inject constructor(private val appContext: Context) : LockBoxPlugin {
private val maxLength: Int = 200 private val maxLength: Int = 50
override fun setBoolean(key: String, value: Boolean) { override fun setBoolean(key: String, value: Boolean) {
SecurePreferences.setValue(appContext, key, value) setChunkedString(key, value.toString())
} }
override fun getBoolean(key: String): Boolean { override fun getBoolean(key: String): Boolean {
return SecurePreferences.getBooleanValue(appContext, key, false) return getChunkedString(key)?.toBoolean() ?: false
} }
override fun setBytes(key: String, value: ByteArray) { override fun setBytes(key: String, value: ByteArray) {
// using hex here because this library doesn't really work well for byte arrays // using hex here because this library doesn't really work well for byte arrays
// but hopefully we can code to arrays and then change the underlying library, later // but hopefully we can code to arrays and then change the underlying library, later
setValue(key, value.toHex()) setChunkedString(key, value.toHex())
} }
override fun getBytes(key: String): ByteArray? { override fun getBytes(key: String): ByteArray? {
return getValue(key)?.fromHex() return getChunkedString(key)?.fromHex()
} }
override fun setCharsUtf8(key: String, value: CharArray) { override fun setCharsUtf8(key: String, value: CharArray) {
// Using string here because this library doesn't work well for char arrays // Using string here because this library doesn't work well for char arrays
// but hopefully we can code to arrays and then change the underlying library, later // but hopefully we can code to arrays and then change the underlying library, later
setValue(key, String(value)) setChunkedString(key, String(value))
} }
override fun getCharsUtf8(key: String): CharArray? { override fun getCharsUtf8(key: String): CharArray? {
return getValue(key)?.toCharArray() return getChunkedString(key)?.toCharArray()
}
fun delete(key: String) {
return SecurePreferences.removeValue(appContext, key)
}
fun clear() {
SecurePreferences.clearAllValues(appContext)
} }
inline operator fun <reified T> set(key: String, value: T) { inline operator fun <reified T> set(key: String, value: T) {
when (T::class.java) { when (T::class) {
Boolean::class.java, Double::class.java, Float::class.java, Integer::class.java, Long::class.java, String::class.java -> setValue(key, value.toString()) Boolean::class -> setBoolean(key, value as Boolean)
else -> throw UnsupportedOperationException("Lockbox does not yet support ${T::class.java.simpleName} objects but it can be added") ByteArray::class -> setBytes(key, value as ByteArray)
CharArray::class -> setCharsUtf8(key, value as CharArray)
Double::class, Float::class, Integer::class, Long::class, String::class -> setChunkedString(key, value.toString())
else -> throw UnsupportedOperationException("Lockbox does not yet support setting ${T::class.java.simpleName} objects but it can easily be added.")
} }
} }
inline operator fun <reified T> get(key: String): T? = when (T::class.java) { inline operator fun <reified T> get(key: String): T? = when (T::class) {
Boolean::class.java -> (getCharsUtf8(key)?.let { String(it).toIntOrNull() }) as T Boolean::class -> getBoolean(key)
Double::class.java -> (getCharsUtf8(key)?.let { String(it).toDoubleOrNull() }) as T ByteArray::class -> getBytes(key)
Float::class.java -> (getCharsUtf8(key)?.let { String(it).toFloatOrNull() }) as T CharArray::class -> getCharsUtf8(key)
Integer::class.java -> (getCharsUtf8(key)?.let { String(it).toIntOrNull() }) as T Double::class -> getChunkedString(key)?.let { it.toDoubleOrNull() }
Long::class.java -> (getCharsUtf8(key)?.let { String(it).toLongOrNull() }) as T Float::class -> getChunkedString(key)?.let { it.toFloatOrNull() }
String::class.java -> (getCharsUtf8(key)?.let { String(it) }) as T Integer::class -> getChunkedString(key)?.let { it.toIntOrNull() }
else -> throw UnsupportedOperationException("Lockbox does not yet support ${T::class.java.simpleName} objects but it can be added") Long::class -> getChunkedString(key)?.let { it.toLongOrNull() }
} String::class -> getChunkedString(key)
else -> throw UnsupportedOperationException("Lockbox does not yet support getting ${T::class.simpleName} objects but it can easily be added")
} as T
/** /**
* Splits a string value into smaller pieces so as not to exceed the limit on the length of * Splits a string value into smaller pieces so as not to exceed the limit on the length of
* String that can be stored. * String that can be stored.
*/ */
fun setValue(key: String, value: String) { fun setChunkedString(key: String, value: String) {
if (value.length > maxLength) { if (value.length > maxLength) {
SecurePreferences.setValue(appContext, key, value.chunked(maxLength).toSet()) SecurePreferences.setValue(appContext, key, value.chunked(maxLength))
} else { } else {
SecurePreferences.setValue(appContext, key, value) SecurePreferences.setValue(appContext, key, value)
} }
@ -77,9 +90,9 @@ class LockBox @Inject constructor(private val appContext: Context) : LockBoxPlug
* *
* @return the key if found and null otherwise. * @return the key if found and null otherwise.
*/ */
private fun getValue(key: String): String? { fun getChunkedString(key: String): String? {
return SecurePreferences.getStringValue(appContext, key, null) return SecurePreferences.getStringValue(appContext, key, null)
?: SecurePreferences.getStringSetValue(appContext, key, setOf()).let { result -> ?: SecurePreferences.getStringListValue(appContext, key, listOf()).let { result ->
if (result.size == 0) null else result.joinToString("") if (result.size == 0) null else result.joinToString("")
} }
} }

View File

@ -1,3 +1,2 @@
<resources> <resources>
<string name="app_name">QRecycler</string>
</resources> </resources>