Merge pull request #207 from zcash/feature/localization-release
Feature/localization release
10
CHANGELOG.md
|
@ -1,6 +1,16 @@
|
|||
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)*
|
||||
------------------------------------
|
||||
- New: Implemented transaction detail view.
|
||||
|
|
|
@ -9,7 +9,7 @@ apply plugin: 'com.google.gms.google-services'
|
|||
|
||||
archivesBaseName = 'zcash-android-wallet'
|
||||
group = 'cash.z.ecc.android'
|
||||
version = '1.0.0-alpha34'
|
||||
version = Deps.versionName
|
||||
|
||||
android {
|
||||
ndkVersion "21.1.6352462"
|
||||
|
@ -17,16 +17,16 @@ android {
|
|||
buildToolsVersion Deps.buildToolsVersion
|
||||
viewBinding.enabled = true
|
||||
defaultConfig {
|
||||
applicationId 'cash.z.ecc.android'
|
||||
applicationId Deps.packageName
|
||||
minSdkVersion Deps.minSdkVersion
|
||||
targetSdkVersion Deps.targetSdkVersion
|
||||
versionCode = 1_00_00_034
|
||||
// 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 = "$version"
|
||||
versionCode = Deps.versionCode
|
||||
versionName = Deps.versionName
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
testInstrumentationRunnerArguments clearPackageData: 'true'
|
||||
multiDexEnabled true
|
||||
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)
|
||||
// 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
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
// mock {
|
||||
// initWith debug
|
||||
// matchingFallbacks = ['debug', 'release', 'zcashtestnet']
|
||||
// }
|
||||
// builds for testing only in the wallet team, typically unfinished features
|
||||
// this flavor can be installed alongside the others
|
||||
qa {
|
||||
initWith debug
|
||||
debuggable true
|
||||
applicationIdSuffix ".internal"
|
||||
resValue 'string', 'file_authority', "${Deps.packageName}.internal.fileprovider"
|
||||
matchingFallbacks = ['debug']
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
|
@ -80,6 +85,9 @@ android {
|
|||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
freeCompilerArgs += "-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi"
|
||||
// freeCompilerArgs += "-Xopt-in=kotlinx.coroutines.ObsoleteCoroutinesApi"
|
||||
// freeCompilerArgs += "-Xopt-in=kotlinx.coroutines.FlowPreview"
|
||||
}
|
||||
kapt {
|
||||
arguments {
|
||||
|
@ -94,7 +102,10 @@ android {
|
|||
}
|
||||
applicationVariants.all { variant ->
|
||||
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.JUNIT
|
||||
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.ESPRESSO
|
||||
}
|
||||
|
||||
defaultTasks 'clean', 'installZcashmainnetRelease'
|
||||
defaultTasks 'clean', 'assembleZcashmainnetRelease'
|
|
@ -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"
|
||||
|
|
|
@ -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 = "1 000"
|
||||
val result = WalletZecFormmatter.toBigDecimal(input)!!
|
||||
Assert.assertEquals(1000, result.longValueExact())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testToBigDecimal_oneWithThinSpace() {
|
||||
val input = "1.000 000"
|
||||
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)
|
||||
}
|
||||
}
|
|
@ -81,8 +81,8 @@ class IntegrationTest {
|
|||
@Test
|
||||
fun testAddress() {
|
||||
val seed = mnemonics.toSeed(phrase.toCharArray())
|
||||
val initializer = Initializer(appContext).apply {
|
||||
new(seed, Initializer.DefaultBirthdayStore(appContext).newWalletBirthday, overwrite = true)
|
||||
val initializer = Initializer(appContext) { config ->
|
||||
config.new(seed)
|
||||
}
|
||||
assertEquals(
|
||||
"Generated incorrect z-address!",
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -22,7 +22,7 @@
|
|||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="cash.z.ecc.android.fileprovider"
|
||||
android:authorities="@string/file_authority"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true"
|
||||
android:writePermission="true">
|
||||
|
|
|
@ -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.TroubleshootingTwig
|
||||
import cash.z.ecc.android.sdk.ext.Twig
|
||||
import cash.z.ecc.android.ui.util.DebugFileTwig
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoSet
|
||||
|
@ -53,7 +54,7 @@ class AppModule {
|
|||
): FeedbackCoordinator {
|
||||
return prefs.getBoolean(Const.Pref.FEEDBACK_ENABLED).let { isEnabled ->
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package cash.z.ecc.android.ext
|
|||
|
||||
object Const {
|
||||
/**
|
||||
* Named objects for Dependency Injection
|
||||
* Named objects for Dependency Injection.
|
||||
*/
|
||||
object Name {
|
||||
/** application data other than cryptographic keys */
|
||||
|
@ -12,7 +12,7 @@ object Const {
|
|||
}
|
||||
|
||||
/**
|
||||
* App preference key names
|
||||
* App preference key names.
|
||||
*/
|
||||
object Pref {
|
||||
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"
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
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.ZcashSdk
|
||||
import cash.z.ecc.android.sdk.ext.convertZatoshiToZec
|
||||
|
@ -11,123 +14,66 @@ import java.text.DecimalFormat
|
|||
import java.text.NumberFormat
|
||||
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 {
|
||||
var ONE_ZEC_IN_ZATOSHI = BigDecimal(ZcashSdk.ZATOSHI_PER_ZEC, MathContext.DECIMAL128)
|
||||
var ZEC_FORMATTER = (NumberFormat.getNumberInstance(Locale("en", "UK")) as DecimalFormat).apply {
|
||||
applyPattern("###.##")
|
||||
roundingMode = RoundingMode.DOWN
|
||||
maximumFractionDigits = 6
|
||||
minimumFractionDigits = 0
|
||||
val ONE_ZEC_IN_ZATOSHI = BigDecimal(ZcashSdk.ZATOSHI_PER_ZEC, MathContext.DECIMAL128)
|
||||
val LONG_SCALE = 8
|
||||
val SHORT_SCALE = 8
|
||||
val SHORT_FORMATTER = from(SHORT_SCALE)
|
||||
val FULL_FORMATTER = from(LONG_SCALE)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Format a Zatoshi value into ZEC with the given number of digits, represented as a string.
|
||||
* 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))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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))
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a BigDecimal 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 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
|
||||
object WalletZecFormmatter {
|
||||
fun toZatoshi(zecString: String): Long? {
|
||||
return toBigDecimal(zecString)?.multiply(Conversions.ONE_ZEC_IN_ZATOSHI, MathContext.DECIMAL128)?.toLong()
|
||||
}
|
||||
fun toZecStringShort(zatoshi: Long?): String {
|
||||
return SHORT_FORMATTER.format((zatoshi ?: 0).toZec())
|
||||
}
|
||||
fun toZecStringFull(zatoshi: Long?): String {
|
||||
return formatFull((zatoshi ?: 0).toZec())
|
||||
}
|
||||
fun formatFull(zec: BigDecimal): String {
|
||||
return FULL_FORMATTER.format(zec)
|
||||
}
|
||||
fun toBigDecimal(zecString: String?): BigDecimal? {
|
||||
if (zecString.isNullOrEmpty()) return BigDecimal.ZERO
|
||||
return try {
|
||||
// ignore commas and whitespace
|
||||
var sanitizedInput = zecString.filter { it.isDigit() or (it == '.') }
|
||||
BigDecimal.ZERO.max(FULL_FORMATTER.parse(sanitizedInput) as BigDecimal)
|
||||
} catch (t: Throwable) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the decimal separator is the last symbol
|
||||
*/
|
||||
inline fun String.endsWithDecimalSeparator(): Boolean {
|
||||
return this.endsWith(ConversionsUniform.ZEC_FORMATTER.decimalFormatSymbols.toString())
|
||||
// convert a zatoshi value to ZEC as a BigDecimal
|
||||
private fun Long?.toZec(): BigDecimal =
|
||||
BigDecimal(this ?: 0L, MathContext.DECIMAL128)
|
||||
.divide(ConversionsUniform.ONE_ZEC_IN_ZATOSHI)
|
||||
.setScale(LONG_SCALE, ConversionsUniform.roundingMode)
|
||||
|
||||
}
|
|
@ -3,26 +3,22 @@ package cash.z.ecc.android.ext
|
|||
import android.app.ActivityManager
|
||||
import android.app.Dialog
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.provider.Settings
|
||||
import android.view.View
|
||||
import androidx.core.content.getSystemService
|
||||
import cash.z.ecc.android.sdk.exception.LightWalletException
|
||||
import cash.z.ecc.android.ui.MainActivity
|
||||
import cash.z.ecc.android.R
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
||||
|
||||
fun Context.showClearDataConfirmation(onDismiss: () -> Unit = {}, onCancel: () -> Unit = {}): Dialog {
|
||||
return MaterialAlertDialogBuilder(this)
|
||||
.setTitle("Nuke Wallet?")
|
||||
.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.")
|
||||
.setTitle(R.string.dialog_nuke_wallet_title)
|
||||
.setMessage(R.string.dialog_nuke_wallet_message)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton("Cancel") { dialog, _ ->
|
||||
.setPositiveButton(R.string.dialog_nuke_wallet_button_positive) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
onDismiss()
|
||||
onCancel()
|
||||
}
|
||||
.setNegativeButton("Erase Wallet") { dialog, _ ->
|
||||
.setNegativeButton(R.string.dialog_nuke_wallet_button_negative) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
onDismiss()
|
||||
getSystemService<ActivityManager>()?.clearApplicationUserData()
|
||||
|
@ -32,15 +28,15 @@ fun Context.showClearDataConfirmation(onDismiss: () -> Unit = {}, onCancel: () -
|
|||
|
||||
fun Context.showUninitializedError(error: Throwable? = null, onDismiss: () -> Unit = {}): Dialog {
|
||||
return MaterialAlertDialogBuilder(this)
|
||||
.setTitle("Wallet Improperly Initialized")
|
||||
.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.")
|
||||
.setTitle(R.string.dialog_error_uninitialized_title)
|
||||
.setMessage(R.string.dialog_error_uninitialized_message)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton("Exit") { dialog, _ ->
|
||||
.setPositiveButton(getString(R.string.dialog_error_uninitialized_button_positive)) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
onDismiss()
|
||||
if (error != null) throw error
|
||||
}
|
||||
.setNegativeButton("Clear Data") { dialog, _ ->
|
||||
.setNegativeButton(getString(R.string.dialog_error_uninitialized_button_negative)) { dialog, _ ->
|
||||
showClearDataConfirmation(onDismiss, onCancel = {
|
||||
// do not let the user back into the app because we cannot recover from this case
|
||||
showUninitializedError(error, onDismiss)
|
||||
|
@ -51,10 +47,10 @@ fun Context.showUninitializedError(error: Throwable? = null, onDismiss: () -> Un
|
|||
|
||||
fun Context.showInvalidSeedPhraseError(error: Throwable? = null, onDismiss: () -> Unit = {}): Dialog {
|
||||
return MaterialAlertDialogBuilder(this)
|
||||
.setTitle("Oops! Invalid Seed Phrase")
|
||||
.setMessage("That seed phrase appears to be invalid! Please double-check it and try again.\n\n${error?.message ?: ""}")
|
||||
.setTitle(R.string.dialog_error_invalid_seed_phrase_title)
|
||||
.setMessage(getString(R.string.dialog_error_invalid_seed_phrase_message, error?.message ?: ""))
|
||||
.setCancelable(false)
|
||||
.setPositiveButton("Retry") { dialog, _ ->
|
||||
.setPositiveButton(getString(R.string.dialog_error_invalid_seed_phrase_button_positive)) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
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 ""}"
|
||||
}
|
||||
return MaterialAlertDialogBuilder(this)
|
||||
.setTitle("Scan Failure")
|
||||
.setTitle(R.string.dialog_error_scan_failure_title)
|
||||
.setMessage(message)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton("Retry") { d, _ ->
|
||||
.setPositiveButton(R.string.dialog_error_scan_failure_button_positive) { d, _ ->
|
||||
d.dismiss()
|
||||
onDismiss()
|
||||
}
|
||||
.setNegativeButton("Ignore") { d, _ ->
|
||||
.setNegativeButton(R.string.dialog_error_scan_failure_button_negative) { d, _ ->
|
||||
d.dismiss()
|
||||
onCancel()
|
||||
onDismiss()
|
||||
|
@ -85,14 +81,14 @@ fun Context.showScanFailure(error: Throwable?, onCancel: () -> Unit = {}, onDism
|
|||
|
||||
fun Context.showCriticalProcessorError(error: Throwable?, onRetry: () -> Unit = {}): Dialog {
|
||||
return MaterialAlertDialogBuilder(this)
|
||||
.setTitle("Processor Error")
|
||||
.setMessage(error?.message ?: "Critical error while processing blocks!")
|
||||
.setTitle(R.string.dialog_error_processor_critical_title)
|
||||
.setMessage(error?.message ?: getString(R.string.dialog_error_processor_critical_message))
|
||||
.setCancelable(false)
|
||||
.setPositiveButton("Retry") { d, _ ->
|
||||
.setPositiveButton(R.string.dialog_error_processor_critical_button_positive) { d, _ ->
|
||||
d.dismiss()
|
||||
onRetry()
|
||||
}
|
||||
.setNegativeButton("Exit") { dialog, _ ->
|
||||
.setNegativeButton(R.string.dialog_error_processor_critical_button_negative) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
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 {
|
||||
return MaterialAlertDialogBuilder(this)
|
||||
.setTitle("Failed to Change Server")
|
||||
.setTitle(R.string.dialog_error_change_server_title)
|
||||
.setMessage(userFacingMessage)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton("Ok") { d, _ ->
|
||||
.setPositiveButton(R.string.dialog_error_change_server_button_positive) { d, _ ->
|
||||
d.dismiss()
|
||||
onConfirm()
|
||||
}
|
||||
.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)
|
||||
.setTitle("Modify Lightwalletd Server?")
|
||||
.setMessage("WARNING: Entering an invalid or untrusted server might result in misconfiguration or loss of funds!")
|
||||
.setTitle(R.string.dialog_modify_server_title)
|
||||
.setMessage(R.string.dialog_modify_server_message)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(positiveText) { dialog, _ ->
|
||||
.setPositiveButton(positiveResId) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
onUpdate()
|
||||
}
|
||||
.setNegativeButton("Cancel") { dialog, _ ->
|
||||
.setNegativeButton(R.string.dialog_modify_server_button_negative) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
onCancel
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ inline fun EditText.limitDecimalPlaces(max: Int) {
|
|||
if (editText.selectionStart == editText.selectionEnd && editText.selectionStart != textStr.length) {
|
||||
textStr = previousValue
|
||||
} else {
|
||||
textStr = number.toZecStringUniform(8)
|
||||
textStr = WalletZecFormmatter.formatFull(number)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
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 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
|
||||
|
||||
inline fun <R> tryWithWarning(message: String = "", block: () -> R): R? {
|
||||
return try {
|
||||
|
@ -11,4 +18,21 @@ inline fun <R> tryWithWarning(message: String = "", block: () -> R): R? {
|
|||
twig("WARNING: $message")
|
||||
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
|
||||
}
|
|
@ -19,8 +19,11 @@ internal inline fun @receiver:ColorRes Int.toAppColor(): Int {
|
|||
/**
|
||||
* Grab a string from the application resources
|
||||
*/
|
||||
internal inline fun @receiver:StringRes Int.toAppString(): String {
|
||||
return ZcashWalletApp.instance.getString(this)}
|
||||
internal inline fun @receiver:StringRes Int.toAppString(lowercase: Boolean = false): String {
|
||||
return ZcashWalletApp.instance.getString(this).let {
|
||||
if (lowercase) it.toLowerCase() else it
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -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.SynchronizerSubcomponent
|
||||
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.FeedbackCoordinator
|
||||
import cash.z.ecc.android.feedback.LaunchMetric
|
||||
|
@ -247,6 +249,9 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
else -> {
|
||||
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 {
|
||||
authenticate(
|
||||
BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle("Authenticate to Proceed")
|
||||
.setTitle(getString(R.string.biometric_prompt_title))
|
||||
.setConfirmationRequired(false)
|
||||
.setDescription(description)
|
||||
.setDeviceCredentialAllowed(true)
|
||||
|
@ -331,7 +336,7 @@ class MainActivity : AppCompatActivity() {
|
|||
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) {
|
||||
val view = findViewById<View>(R.id.main_activity_container)
|
||||
val snacks = Snackbar
|
||||
|
@ -410,7 +415,7 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
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
|
||||
|
@ -422,15 +427,9 @@ class MainActivity : AppCompatActivity() {
|
|||
if (dialog == null) {
|
||||
notified = true
|
||||
runOnUiThread {
|
||||
dialog = MaterialAlertDialogBuilder(this)
|
||||
.setTitle("Wallet Improperly Initialized")
|
||||
.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()
|
||||
dialog = showUninitializedError(error) {
|
||||
dialog = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -438,20 +437,10 @@ class MainActivity : AppCompatActivity() {
|
|||
if (dialog == null && !ignoreScanFailure) throttle("scanFailure", 20_000L) {
|
||||
notified = true
|
||||
runOnUiThread {
|
||||
dialog = MaterialAlertDialogBuilder(this)
|
||||
.setTitle("Scan Failure")
|
||||
.setMessage("${error.message}${if (error.cause != null) "\n\nCaused by: ${error.cause}" else ""}")
|
||||
.setCancelable(true)
|
||||
.setPositiveButton("Retry") { d, _ ->
|
||||
d.dismiss()
|
||||
dialog = null
|
||||
}
|
||||
.setNegativeButton("Ignore") { d, _ ->
|
||||
d.dismiss()
|
||||
ignoreScanFailure = true
|
||||
dialog = null
|
||||
}
|
||||
.show()
|
||||
dialog = showScanFailure(error,
|
||||
onCancel = { dialog = null },
|
||||
onDismiss = { dialog = null }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -462,20 +451,9 @@ class MainActivity : AppCompatActivity() {
|
|||
if (dialog == null) {
|
||||
notified = true
|
||||
runOnUiThread {
|
||||
dialog = MaterialAlertDialogBuilder(this)
|
||||
.setTitle("Processor Error")
|
||||
.setMessage(error?.message ?: "Critical error while processing blocks!")
|
||||
.setCancelable(false)
|
||||
.setPositiveButton("Retry") { d, _ ->
|
||||
d.dismiss()
|
||||
dialog = null
|
||||
}
|
||||
.setNegativeButton("Exit") { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
throw error
|
||||
?: RuntimeException("Critical error while processing blocks and the user chose to exit.")
|
||||
}
|
||||
.show()
|
||||
dialog = showCriticalProcessorError(error) {
|
||||
dialog = null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -604,7 +582,7 @@ class MainActivity : AppCompatActivity() {
|
|||
try {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))
|
||||
} 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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,9 +10,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import cash.z.ecc.android.R
|
||||
import cash.z.ecc.android.databinding.FragmentHistoryBinding
|
||||
import cash.z.ecc.android.di.viewmodel.activityViewModel
|
||||
import cash.z.ecc.android.ext.goneIf
|
||||
import cash.z.ecc.android.ext.onClickNavUp
|
||||
import cash.z.ecc.android.ext.toColoredSpan
|
||||
import cash.z.ecc.android.ext.*
|
||||
import cash.z.ecc.android.feedback.Report
|
||||
import cash.z.ecc.android.feedback.Report.Tap.HISTORY_BACK
|
||||
import cash.z.ecc.android.sdk.block.CompactBlockProcessor.WalletBalance
|
||||
|
@ -44,7 +42,7 @@ class HistoryFragment : BaseFragment<FragmentHistoryBinding>() {
|
|||
initTransactionUI()
|
||||
binding.backButtonHitArea.onClickNavUp { tapped(HISTORY_BACK) }
|
||||
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) {
|
||||
binding.textBalanceAvailable.text = balance.availableZatoshi.convertZatoshiToZecString()
|
||||
binding.textBalanceAvailable.text = WalletZecFormmatter.toZecStringShort(balance.availableZatoshi)
|
||||
val change = (balance.totalZatoshi - balance.availableZatoshi)
|
||||
binding.textBalanceDescription.apply {
|
||||
goneIf(change <= 0L)
|
||||
val changeString = change.convertZatoshiToZecString()
|
||||
text = "(expecting +$changeString ZEC)".toColoredSpan(R.color.text_light, "+${changeString}")
|
||||
val changeString = WalletZecFormmatter.toZecStringFull(change)
|
||||
val expecting = R.string.home_banner_expecting.toAppString(true)
|
||||
text = "($expecting +$changeString ZEC)".toColoredSpan(R.color.text_light, "+${changeString}")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,10 @@ import cash.z.ecc.android.di.viewmodel.activityViewModel
|
|||
import cash.z.ecc.android.ext.*
|
||||
import cash.z.ecc.android.feedback.Report
|
||||
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.base.BaseFragment
|
||||
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
|
||||
uiModel.confirmation?.let {
|
||||
subwaySpotConfirmations.visible(); subwayLabelConfirmations.visible();
|
||||
subwaySpotConfirmations.visible(); subwayLabelConfirmations.visible()
|
||||
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())
|
||||
} else {
|
||||
subwayLabelConfirmations.setTextColor(R.color.tx_text_light_dimmed.toAppColor())
|
||||
|
@ -196,13 +199,11 @@ class TransactionFragment : BaseFragment<FragmentTransactionBinding>() {
|
|||
else -> null
|
||||
}
|
||||
isMined = tx?.minedHeight != null && tx.minedHeight > ZcashSdk.SAPLING_ACTIVATION_HEIGHT
|
||||
topValue = if (tx == null) "" else "\$${tx?.value.convertZatoshiToZecString()}"
|
||||
minedHeight = NumberFormat.getNumberInstance(Locale.getDefault()).format(
|
||||
tx?.minedHeight ?: 0
|
||||
)
|
||||
topValue = if (tx == null) "" else "\$${WalletZecFormmatter.toZecStringFull(tx.value)}"
|
||||
minedHeight = (tx?.minedHeight ?: 0).toString()
|
||||
val flags =
|
||||
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,
|
||||
tx.blockTimeInSeconds * 1000,
|
||||
DateUtils.SECOND_IN_MILLIS,
|
||||
|
@ -212,7 +213,7 @@ class TransactionFragment : BaseFragment<FragmentTransactionBinding>() {
|
|||
|
||||
// memo logic
|
||||
val txMemo = tx?.memo.toUtf8Memo()
|
||||
if (!txMemo.isNullOrEmpty()) {
|
||||
if (!txMemo.isEmpty()) {
|
||||
memo = txMemo
|
||||
}
|
||||
|
||||
|
@ -224,18 +225,18 @@ class TransactionFragment : BaseFragment<FragmentTransactionBinding>() {
|
|||
val hasLatestHeight = latestHeight != null && latestHeight > ZcashSdk.SAPLING_ACTIVATION_HEIGHT
|
||||
if (it.minedHeight > 0 && hasLatestHeight) {
|
||||
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 {
|
||||
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")
|
||||
confirmation = "Confirmed"
|
||||
confirmation = getString(R.string.transaction_status_confirmed)
|
||||
} else {
|
||||
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 {
|
||||
confirmation = "Pending"
|
||||
confirmation = getString(R.string.transaction_status_pending)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -244,20 +245,20 @@ class TransactionFragment : BaseFragment<FragmentTransactionBinding>() {
|
|||
// inbound v. outbound values
|
||||
when (isInbound) {
|
||||
true -> {
|
||||
topLabel = "You Received"
|
||||
bottomLabel = "Total Received"
|
||||
bottomValue = "\$${tx?.value.convertZatoshiToZecString()}"
|
||||
topLabel = getString(R.string.transaction_story_inbound)
|
||||
bottomLabel = getString(R.string.transaction_story_inbound_total)
|
||||
bottomValue = "\$${WalletZecFormmatter.toZecStringFull(tx?.value)}"
|
||||
iconRotation = 315f
|
||||
source = "to your shielded wallet"
|
||||
source = getString(R.string.transaction_story_to_shielded)
|
||||
address = mainActivity.extractValidAddress(tx?.memo.toUtf8Memo())
|
||||
}
|
||||
false -> {
|
||||
topLabel = "You Sent"
|
||||
bottomLabel = "Total Sent"
|
||||
bottomValue = "\$${tx?.value?.plus(ZcashSdk.MINERS_FEE_ZATOSHI).convertZatoshiToZecString()}"
|
||||
topLabel = getString(R.string.transaction_story_outbound)
|
||||
bottomLabel = getString(R.string.transaction_story_outbound_total)
|
||||
bottomValue = "\$${WalletZecFormmatter.toZecStringFull(tx?.value?.plus(ZcashSdk.MINERS_FEE_ZATOSHI))}"
|
||||
iconRotation = 135f
|
||||
fee = "+0.0001 network fee"
|
||||
source = "from your shielded wallet"
|
||||
fee = getString(R.string.transaction_story_network_fee, WalletZecFormmatter.toZecStringFull(ZcashSdk.MINERS_FEE_ZATOSHI))
|
||||
source = getString(R.string.transaction_story_from_shielded)
|
||||
address = tx?.toAddress
|
||||
}
|
||||
null -> {
|
||||
|
|
|
@ -2,10 +2,14 @@ package cash.z.ecc.android.ui.history
|
|||
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
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.locale
|
||||
import cash.z.ecc.android.ext.toAppColor
|
||||
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
|
||||
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 kotlinx.coroutines.launch
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
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 bottomText = itemView.findViewById<TextView>(R.id.text_transaction_bottom)
|
||||
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?) {
|
||||
val mainActivity = itemView.context as MainActivity
|
||||
mainActivity.lifecycleScope.launch {
|
||||
// update view
|
||||
var lineOne: String = ""
|
||||
var lineTwo: String = ""
|
||||
var amountZec: String = ""
|
||||
var amountDisplay: String = ""
|
||||
var lineOne = ""
|
||||
var lineTwo = ""
|
||||
var amountZec = ""
|
||||
var amountDisplay = ""
|
||||
var amountColor: Int = R.color.text_light_dimmed
|
||||
var lineOneColor: Int = R.color.text_light
|
||||
var lineTwoColor: Int = R.color.text_light_dimmed
|
||||
|
@ -47,16 +50,16 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
|||
onTransactionLongPressed(this)
|
||||
true
|
||||
}
|
||||
amountZec = value.convertZatoshiToZecString()
|
||||
amountZec = WalletZecFormmatter.toZecStringShort(value)
|
||||
// TODO: these might be good extension functions
|
||||
val timestamp = formatter.format(blockTimeInSeconds * 1000L)
|
||||
val isMined = blockTimeInSeconds != 0L
|
||||
when {
|
||||
!toAddress.isNullOrEmpty() -> {
|
||||
lineOne = "You paid ${toAddress?.toAbbreviatedAddress()}"
|
||||
lineTwo = if (isMined) "Sent $timestamp" else "Pending confirmation"
|
||||
lineOne = "${str(R.string.transaction_address_you_paid)} ${toAddress?.toAbbreviatedAddress()}"
|
||||
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?)
|
||||
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"
|
||||
if (isMined) {
|
||||
amountColor = R.color.zcashRed
|
||||
|
@ -67,23 +70,23 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
|||
}
|
||||
}
|
||||
toAddress.isNullOrEmpty() && value > 0L && minedHeight > 0 -> {
|
||||
lineOne = "${mainActivity.getSender(transaction)} paid you"
|
||||
lineTwo = "Received $timestamp"
|
||||
lineOne = "${mainActivity.getSender(transaction)} ${str(R.string.transaction_address_paid_you)}"
|
||||
lineTwo = "${str(R.string.transaction_received)} $timestamp"
|
||||
amountDisplay = "+ $amountZec"
|
||||
amountColor = R.color.zcashGreen
|
||||
indicatorBackground = R.drawable.background_indicator_inbound
|
||||
}
|
||||
else -> {
|
||||
lineOne = "Unknown"
|
||||
lineTwo = "Unknown"
|
||||
amountDisplay = "$amountZec"
|
||||
lineOne = str(R.string.unknown)
|
||||
lineTwo = str(R.string.unknown)
|
||||
amountDisplay = amountZec
|
||||
amountColor = R.color.text_light
|
||||
}
|
||||
}
|
||||
// sanitize amount
|
||||
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
|
||||
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())
|
||||
bottomText.setTextColor(lineTwoColor.toAppColor())
|
||||
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
|
||||
shieldIcon.goneIf((transaction?.raw != null || transaction?.expiryHeight != null) && !transaction?.toAddress.isShielded())
|
||||
shieldIcon.goneIf((transaction?.raw != null || transaction?.expiryHeight != null) && !transaction.toAddress.isShielded())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,6 +117,8 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
|||
mainActivity.copyText(it, "Transaction Address")
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun str(@StringRes resourceId: Int) = itemView.context.getString(resourceId)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import cash.z.ecc.android.R
|
||||
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.WalletSetupState.NO_SEED
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.coroutines.flow.scanReduce
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
@ -65,7 +64,7 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||
mainActivity?.safeNavigate(R.id.action_nav_home_to_create_wallet)
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
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()
|
||||
twig("HomeFragment.onResume resumeScope.isActive: ${resumedScope.isActive} $resumedScope")
|
||||
val existingAmount = sendViewModel.zatoshiAmount.coerceAtLeast(0)
|
||||
viewModel.initializeMaybe(existingAmount.convertZatoshiToZecStringUniform(8))
|
||||
viewModel.initializeMaybe(WalletZecFormmatter.toZecStringFull(existingAmount))
|
||||
if (existingAmount == 0L) onClearAmount()
|
||||
viewModel.uiModels.scanReduce { old, new ->
|
||||
viewModel.uiModels.runningReduce { old, new ->
|
||||
onModelUpdated(old, new)
|
||||
new
|
||||
}.onCompletion {
|
||||
|
@ -157,9 +156,9 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
twig("HomeFragment.onSaveInstanceState")
|
||||
if (::uiModel.isInitialized) {
|
||||
// if (::uiModel.isInitialized) {
|
||||
// outState.putParcelable("uiModel", uiModel)
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
override fun onViewStateRestored(savedInstanceState: Bundle?) {
|
||||
|
@ -204,20 +203,20 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||
}
|
||||
|
||||
val sendText = when {
|
||||
uiModel.status == DISCONNECTED -> "Reconnecting . . ."
|
||||
uiModel.isSynced -> if (uiModel.hasFunds) "SEND AMOUNT" else "NO FUNDS AVAILABLE"
|
||||
uiModel.status == STOPPED -> "IDLE"
|
||||
uiModel.isDownloading -> "Downloading . . . ${snake.downloadProgress}%"
|
||||
uiModel.isValidating -> "Validating . . ."
|
||||
uiModel.isScanning -> "Scanning . . . ${snake.scanProgress}%"
|
||||
else -> "Updating"
|
||||
uiModel.status == DISCONNECTED -> getString(R.string.home_button_send_disconnected)
|
||||
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 -> getString(R.string.home_button_send_idle)
|
||||
uiModel.isDownloading -> getString(R.string.home_button_send_downloading, snake.downloadProgress)
|
||||
uiModel.isValidating -> getString(R.string.home_button_send_validating)
|
||||
uiModel.isScanning -> getString(R.string.home_button_send_scanning, snake.scanProgress)
|
||||
else -> getString(R.string.home_button_send_updating)
|
||||
}
|
||||
|
||||
binding.buttonSendAmount.text = 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
|
||||
binding.buttonSendAmount.setTextColor(resources.getColorStateList(resId))
|
||||
context?.let { binding.buttonSendAmount.setTextColor(AppCompatResources.getColorStateList(it, resId)) }
|
||||
binding.lottieButtonLoading.invisibleIf(uiModel.isDisconnected)
|
||||
}
|
||||
|
||||
|
@ -229,23 +228,24 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||
binding.textSendAmount.text = "\$$amount".toColoredSpan(R.color.text_light_dimmed, "$")
|
||||
if (updateModel) {
|
||||
sendViewModel.zatoshiAmount = amount.safelyConvertToBigDecimal().convertZecToZatoshi()
|
||||
twig("dBUG: updating model. converting: $amount\tresult: ${sendViewModel.zatoshiAmount}\tprint: ${WalletZecFormmatter.toZecStringFull(sendViewModel.zatoshiAmount)}")
|
||||
}
|
||||
binding.buttonSendAmount.disabledIf(amount == "0")
|
||||
}
|
||||
|
||||
fun setAvailable(availableBalance: Long = -1L, totalBalance: Long = -1L) {
|
||||
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.transparentIf(missingBalance)
|
||||
binding.labelBalance.transparentIf(missingBalance)
|
||||
binding.textBalanceDescription.apply {
|
||||
goneIf(missingBalance)
|
||||
text = if (availableBalance != -1L && (availableBalance < totalBalance)) {
|
||||
val change = (totalBalance - availableBalance).convertZatoshiToZecString()
|
||||
"(expecting +$change ZEC)".toColoredSpan(R.color.text_light, "+$change")
|
||||
val change = WalletZecFormmatter.toZecStringFull(totalBalance - availableBalance)
|
||||
"(${getString(R.string.home_banner_expecting)} +$change ZEC)".toColoredSpan(R.color.text_light, "+$change")
|
||||
} else {
|
||||
"(enter an amount to send)"
|
||||
getString(R.string.home_instruction_enter_amount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -332,10 +332,10 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||
when (action) {
|
||||
FUND_NOW -> {
|
||||
MaterialAlertDialogBuilder(activity)
|
||||
.setMessage("To make full use of this wallet, deposit funds to your address.")
|
||||
.setTitle("No Balance")
|
||||
.setMessage(R.string.home_dialog_no_balance_message)
|
||||
.setTitle(R.string.home_dialog_no_balance_title)
|
||||
.setCancelable(true)
|
||||
.setPositiveButton("View Address") { dialog, _ ->
|
||||
.setPositiveButton(R.string.home_dialog_no_balance_button_positive) { dialog, _ ->
|
||||
tapped(HOME_FUND_NOW)
|
||||
dialog.dismiss()
|
||||
mainActivity?.safeNavigate(R.id.action_nav_home_to_nav_receive)
|
||||
|
@ -363,7 +363,7 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||
}
|
||||
|
||||
private fun onNoFunds() {
|
||||
setBanner("No Balance", FUND_NOW)
|
||||
setBanner(getString(R.string.home_no_balance), FUND_NOW)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package cash.z.ecc.android.ui.home
|
||||
|
||||
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.Synchronizer
|
||||
import cash.z.ecc.android.sdk.Synchronizer.Status.*
|
||||
|
@ -37,25 +39,26 @@ class HomeViewModel @Inject constructor() : ViewModel() {
|
|||
}
|
||||
_typedChars = ConflatedBroadcastChannel()
|
||||
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 ->
|
||||
when {
|
||||
// no-op cases
|
||||
acc == "0" && c == '0'
|
||||
|| (c == '<' && acc == "0")
|
||||
|| (c == '.' && acc.contains('.')) -> {twig("triggered: 1 acc: $acc c: $c")
|
||||
|| (c == backspace && acc == "0")
|
||||
|| (c == decimal && acc.contains(decimal)) -> {twig("triggered: 1 acc: $acc c: $c")
|
||||
acc
|
||||
}
|
||||
c == '<' && acc.length <= 1 -> {twig("triggered: 2 $typedChars")
|
||||
c == backspace && acc.length <= 1 -> {twig("triggered: 2 $typedChars")
|
||||
"0"
|
||||
}
|
||||
c == '<' -> {twig("triggered: 3")
|
||||
c == backspace -> {twig("triggered: 3")
|
||||
acc.substring(0, acc.length - 1)
|
||||
}
|
||||
acc == "0" && c != '.' -> {twig("triggered: 4 $typedChars")
|
||||
acc == "0" && c != decimal -> {twig("triggered: 4 $typedChars")
|
||||
c.toString()
|
||||
}
|
||||
acc.contains('.') && acc.length - acc.indexOf('.') > 8 -> {
|
||||
acc.contains(decimal) && acc.length - acc.indexOf(decimal) > 8 -> {
|
||||
twig("triggered: 5 $typedChars")
|
||||
acc
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import android.view.ViewTreeObserver
|
|||
import android.view.WindowManager
|
||||
import android.widget.Toast
|
||||
import androidx.core.view.doOnLayout
|
||||
import cash.z.ecc.android.R
|
||||
import cash.z.ecc.android.databinding.FragmentFeedbackBinding
|
||||
import cash.z.ecc.android.di.viewmodel.viewModel
|
||||
import cash.z.ecc.android.feedback.Report
|
||||
|
@ -67,7 +68,7 @@ class FeedbackFragment : BaseFragment<FragmentFeedbackBinding>() {
|
|||
//
|
||||
|
||||
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)
|
||||
|
||||
val q1 = binding.inputQuestion1.editText?.text.toString()
|
||||
|
|
|
@ -11,6 +11,7 @@ import cash.z.ecc.android.R
|
|||
import cash.z.ecc.android.ZcashWalletApp
|
||||
import cash.z.ecc.android.databinding.FragmentProfileBinding
|
||||
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.onClickNavBack
|
||||
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.Funnel.UserFeedback
|
||||
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.twig
|
||||
import cash.z.ecc.android.ui.base.BaseFragment
|
||||
import cash.z.ecc.android.ui.util.DebugFileTwig
|
||||
import kotlinx.coroutines.launch
|
||||
import okio.Okio
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.lang.IllegalArgumentException
|
||||
|
||||
|
||||
class ProfileFragment : BaseFragment<FragmentProfileBinding>() {
|
||||
|
@ -70,7 +69,13 @@ class ProfileFragment : BaseFragment<FragmentProfileBinding>() {
|
|||
}
|
||||
|
||||
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?) {
|
||||
|
@ -85,7 +90,7 @@ class ProfileFragment : BaseFragment<FragmentProfileBinding>() {
|
|||
putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris)
|
||||
type = "text/*"
|
||||
}
|
||||
startActivity(Intent.createChooser(intent, "Share Log Files"))
|
||||
startActivity(Intent.createChooser(intent, getString(R.string.profile_share_log_title)))
|
||||
}
|
||||
|
||||
fun shareFile(file: File?) {
|
||||
|
@ -95,36 +100,14 @@ class ProfileFragment : BaseFragment<FragmentProfileBinding>() {
|
|||
putExtra(Intent.EXTRA_STREAM, uri)
|
||||
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? {
|
||||
return mainActivity?.feedbackCoordinator?.findObserver<FeedbackFile>()?.file
|
||||
}
|
||||
|
||||
private fun loadLogFileAsText(): String? {
|
||||
val feedbackFile: File = userLogFile() ?: return null
|
||||
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
|
||||
private fun developerLogFile(): File? {
|
||||
return Bush.trunk.find<DebugFileTwig>()?.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.viewModel
|
||||
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.Tap.SCAN_BACK
|
||||
import cash.z.ecc.android.sdk.ext.ZcashSdk
|
||||
|
@ -47,8 +46,8 @@ class ScanFragment : BaseFragment<FragmentScanBinding>() {
|
|||
binding.backButtonHitArea.onClickNavBack() { tapped(SCAN_BACK) }
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
if (!allPermissionsGranted()) getRuntimePermissions()
|
||||
}
|
||||
|
||||
|
@ -125,7 +124,7 @@ class ScanFragment : BaseFragment<FragmentScanBinding>() {
|
|||
resumedScope.launch {
|
||||
if (viewModel.isNotValid(qrContent)) {
|
||||
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()
|
||||
} else { /* continue scanning*/
|
||||
binding.textScanError.text = ""
|
||||
|
|
|
@ -52,7 +52,7 @@ class SendAddressFragment : BaseFragment<FragmentSendAddressBinding>(),
|
|||
|
||||
// Apply View Model
|
||||
if (sendViewModel.zatoshiAmount > 0L) {
|
||||
sendViewModel.zatoshiAmount.convertZatoshiToZecStringUniform(8).let { amount ->
|
||||
WalletZecFormmatter.toZecStringFull(sendViewModel.zatoshiAmount).let { amount ->
|
||||
binding.inputZcashAmount.setText(amount)
|
||||
}
|
||||
} else {
|
||||
|
@ -123,7 +123,7 @@ class SendAddressFragment : BaseFragment<FragmentSendAddressBinding>(),
|
|||
private fun onMax() {
|
||||
if (maxZatoshi != null) {
|
||||
binding.inputZcashAmount.apply {
|
||||
setText(maxZatoshi.convertZatoshiToZecString(8))
|
||||
setText(WalletZecFormmatter.toZecStringFull(maxZatoshi ?: 0L))
|
||||
postDelayed({
|
||||
requestFocus()
|
||||
setSelection(text?.length ?: 0)
|
||||
|
@ -156,7 +156,7 @@ class SendAddressFragment : BaseFragment<FragmentSendAddressBinding>(),
|
|||
|
||||
private fun onBalanceUpdated(balance: WalletBalance) {
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import androidx.lifecycle.lifecycleScope
|
|||
import cash.z.ecc.android.R
|
||||
import cash.z.ecc.android.databinding.FragmentSendConfirmBinding
|
||||
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.onClickNavTo
|
||||
import cash.z.ecc.android.feedback.Report
|
||||
|
@ -36,7 +37,7 @@ class SendConfirmFragment : BaseFragment<FragmentSendConfirmBinding>() {
|
|||
// }
|
||||
mainActivity?.lifecycleScope?.launch {
|
||||
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 ->
|
||||
binding.radioIncludeAddress.isChecked = hasMemo || sendViewModel.includeFromAddress
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package cash.z.ecc.android.ui.send
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
|
@ -8,6 +9,7 @@ import androidx.lifecycle.lifecycleScope
|
|||
import cash.z.ecc.android.R
|
||||
import cash.z.ecc.android.databinding.FragmentSendFinalBinding
|
||||
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.feedback.Report
|
||||
import cash.z.ecc.android.feedback.Report.Tap.SEND_FINAL_CLOSE
|
||||
|
@ -23,11 +25,12 @@ import kotlinx.coroutines.flow.onEach
|
|||
class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
|
||||
override val screen = Report.Screen.SEND_FINAL
|
||||
|
||||
val sendViewModel: SendViewModel by activityViewModel()
|
||||
private val sendViewModel: SendViewModel by activityViewModel()
|
||||
|
||||
override fun inflate(inflater: LayoutInflater): FragmentSendFinalBinding =
|
||||
FragmentSendFinalBinding.inflate(inflater)
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding.buttonPrimary.setOnClickListener {
|
||||
|
@ -40,7 +43,7 @@ class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
|
|||
onExit().also { tapped(SEND_FINAL_CLOSE) }
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -88,7 +91,7 @@ class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
|
|||
|
||||
private fun onExit() {
|
||||
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) {
|
||||
|
@ -96,7 +99,7 @@ class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
|
|||
}
|
||||
|
||||
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() {
|
||||
|
@ -107,30 +110,31 @@ class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
|
|||
private fun PendingTransaction.toUiModel() = UiModel().also { model ->
|
||||
when {
|
||||
isCancelled() -> {
|
||||
model.title = "Cancelled."
|
||||
model.primaryButtonText = "Go Back"
|
||||
model.title = getString(R.string.send_final_result_cancelled)
|
||||
model.primaryButtonText = getString(R.string.send_final_button_primary_back)
|
||||
model.primaryAction = { onReturnToSend() }
|
||||
}
|
||||
isSubmitSuccess() -> {
|
||||
model.title = "SENT!"
|
||||
model.primaryButtonText = "See Details"
|
||||
model.title = getString(R.string.send_final_button_primary_sent)
|
||||
model.primaryButtonText = getString(R.string.send_final_button_primary_details)
|
||||
model.primaryAction = { onSeeDetails() }
|
||||
}
|
||||
isFailure() -> {
|
||||
model.title = "Failed."
|
||||
model.errorMessage = if (isFailedEncoding()) "The transaction could not be encoded." else "Unable to submit transaction to the network."
|
||||
model.primaryButtonText = "Retry"
|
||||
model.title = getString(R.string.send_final_button_primary_failed)
|
||||
model.errorMessage = if (isFailedEncoding()) getString(R.string.send_final_error_encoding) else getString(
|
||||
R.string.send_final_error_submitting)
|
||||
model.primaryButtonText = getString(R.string.send_final_button_primary_retry)
|
||||
model.primaryAction = { onReturnToSend() }
|
||||
}
|
||||
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
|
||||
if (isCreating()) {
|
||||
model.showCloseIcon = false
|
||||
model.primaryButtonText = "Cancel"
|
||||
model.primaryButtonText = getString(R.string.send_final_button_primary_cancel)
|
||||
model.primaryAction = { onCancel(this) }
|
||||
} else {
|
||||
model.primaryButtonText = "See Details"
|
||||
model.primaryButtonText = getString(R.string.send_final_button_primary_details)
|
||||
model.primaryAction = { onSeeDetails() }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import androidx.core.view.isGone
|
|||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.ImageViewCompat
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import cash.z.ecc.android.R
|
||||
import cash.z.ecc.android.databinding.FragmentSendBinding
|
||||
|
@ -105,7 +106,7 @@ class SendFragment : BaseFragment<FragmentSendBinding>(),
|
|||
private fun applyViewModel(model: SendViewModel) {
|
||||
// apply amount
|
||||
val roundedAmount =
|
||||
model.zatoshiAmount.coerceAtLeast(0L).convertZatoshiToZecStringUniform(8)
|
||||
WalletZecFormmatter.toZecStringFull(model.zatoshiAmount.coerceAtLeast(0L))
|
||||
binding.textSendAmount.text = "\$$roundedAmount"
|
||||
// apply address
|
||||
binding.inputZcashAddress.setText(model.toAddress)
|
||||
|
@ -117,7 +118,7 @@ class SendFragment : BaseFragment<FragmentSendBinding>(),
|
|||
|
||||
private fun onMemoUpdated() {
|
||||
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
|
||||
binding.textLayoutMemo.setHelperTextColor(ColorStateList.valueOf(color.toAppColor()))
|
||||
}
|
||||
|
@ -135,17 +136,17 @@ class SendFragment : BaseFragment<FragmentSendBinding>(),
|
|||
}
|
||||
|
||||
private fun onAddressChanged(address: String) {
|
||||
resumedScope.launch {
|
||||
lifecycleScope.launchWhenResumed {
|
||||
val validation = sendViewModel.validateAddress(address)
|
||||
binding.buttonSend.isActivated = !validation.isNotValid
|
||||
var type = when (validation) {
|
||||
is AddressType.Transparent -> "This is a valid transparent address" to R.color.zcashGreen
|
||||
is AddressType.Shielded -> "This is a valid shielded address" to R.color.zcashGreen
|
||||
is AddressType.Invalid -> "This address appears to be invalid" to R.color.zcashRed
|
||||
is AddressType.Transparent -> R.string.send_validation_address_valid_taddr to R.color.zcashGreen
|
||||
is AddressType.Shielded -> R.string.send_validation_address_valid_zaddr to R.color.zcashGreen
|
||||
is AddressType.Invalid -> R.string.send_validation_address_invalid to R.color.zcashRed
|
||||
}
|
||||
if (address == sendViewModel.synchronizer.getAddress()) type =
|
||||
"Warning, this appears to be your address!" to R.color.zcashRed
|
||||
binding.textLayoutAddress.helperText = type.first
|
||||
R.string.send_validation_address_self to R.color.zcashRed
|
||||
binding.textLayoutAddress.helperText = getString(type.first)
|
||||
binding.textLayoutAddress.setHelperTextColor(ColorStateList.valueOf(type.second.toAppColor()))
|
||||
|
||||
// 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) {
|
||||
sendViewModel.toAddress = binding.inputZcashAddress.text.toString()
|
||||
sendViewModel.validate(availableZatoshi, maxZatoshi).onFirstWith(resumedScope) { errorMessage ->
|
||||
sendViewModel.validate(requireContext(), availableZatoshi, maxZatoshi).onFirstWith(resumedScope) { errorMessage ->
|
||||
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)
|
||||
mainActivity?.safeNavigate(R.id.action_nav_send_to_nav_send_final)
|
||||
}
|
||||
|
@ -189,7 +190,7 @@ class SendFragment : BaseFragment<FragmentSendBinding>(),
|
|||
private fun onMax() {
|
||||
if (maxZatoshi != null) {
|
||||
// binding.inputZcashAmount.apply {
|
||||
// setText(maxZatoshi.convertZatoshiToZecString(8))
|
||||
// setText(WalletZecFormmatter.toZecStringFull(maxZatoshi))
|
||||
// postDelayed({
|
||||
// requestFocus()
|
||||
// setSelection(text?.length ?: 0)
|
||||
|
@ -223,7 +224,7 @@ class SendFragment : BaseFragment<FragmentSendBinding>(),
|
|||
|
||||
private fun onBalanceUpdated(balance: WalletBalance) {
|
||||
// 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)
|
||||
availableZatoshi = balance.availableZatoshi
|
||||
}
|
||||
|
@ -264,7 +265,7 @@ class SendFragment : BaseFragment<FragmentSendBinding>(),
|
|||
selected,
|
||||
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(
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
package cash.z.ecc.android.ui.send
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.ViewModel
|
||||
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.Keyed
|
||||
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.lockbox.LockBox
|
||||
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.ext.ZcashSdk
|
||||
import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString
|
||||
|
@ -31,7 +36,8 @@ import javax.inject.Inject
|
|||
|
||||
class SendViewModel @Inject constructor() : ViewModel() {
|
||||
|
||||
private val metrics = mutableMapOf<String, TimeMetric>()
|
||||
// note used in testing
|
||||
val metrics = mutableMapOf<String, TimeMetric>()
|
||||
|
||||
@Inject
|
||||
lateinit var lockBox: LockBox
|
||||
|
@ -60,7 +66,7 @@ class SendViewModel @Inject constructor() : ViewModel() {
|
|||
funnel(SendSelected)
|
||||
val memoToSend = createMemoToSend()
|
||||
val keys = DerivationTool.deriveSpendingKeys(
|
||||
lockBox.getBytes(WalletSetupViewModel.LockBoxKey.SEED)!!
|
||||
lockBox.getBytes(Const.Backup.SEED)!!
|
||||
)
|
||||
funnel(SpendingKeyFound)
|
||||
reportIssues(memoToSend)
|
||||
|
@ -100,29 +106,29 @@ class SendViewModel @Inject constructor() : ViewModel() {
|
|||
suspend fun validateAddress(address: String): AddressType =
|
||||
synchronizer.validateAddress(address)
|
||||
|
||||
fun validate(availableZatoshi: Long?, maxZatoshi: Long?) = flow<String?> {
|
||||
fun validate(context: Context, availableZatoshi: Long?, maxZatoshi: Long?) = flow<String?> {
|
||||
|
||||
when {
|
||||
synchronizer.validateAddress(toAddress).isNotValid -> {
|
||||
emit("Please enter a valid address.")
|
||||
emit(context.getString(R.string.send_validation_error_address_invalid))
|
||||
}
|
||||
zatoshiAmount < 1 -> {
|
||||
emit("Please go back and enter at least 1 Zatoshi.")
|
||||
emit(context.getString(R.string.send_validation_error_amount_minimum))
|
||||
}
|
||||
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 -> {
|
||||
emit("No funds available to send.")
|
||||
emit(context.getString(R.string.send_validation_error_no_available_funds))
|
||||
}
|
||||
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 -> {
|
||||
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 -> {
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding>() {
|
|||
|
||||
private fun onResetClicked(unused: View?) {
|
||||
mainActivity?.hideKeyboard()
|
||||
context?.showUpdateServerDialog("Restore Defaults") {
|
||||
context?.showUpdateServerDialog(R.string.settings_buttons_restore) {
|
||||
resumedScope.launch {
|
||||
binding.groupLoading.visible()
|
||||
binding.loadingView.requestFocus()
|
||||
|
@ -117,7 +117,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding>() {
|
|||
if (uiModel.complete) {
|
||||
binding.groupLoading.gone()
|
||||
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
|
||||
}
|
||||
false
|
||||
|
@ -133,7 +133,7 @@ class SettingsFragment : BaseFragment<FragmentSettingsBinding>() {
|
|||
val message = "An error occured while changing servers. Please verify the info" +
|
||||
" and try again.\n\nError: $details"
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -10,9 +10,11 @@ import android.widget.TextView
|
|||
import android.widget.Toast
|
||||
import androidx.activity.addCallback
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import cash.z.ecc.android.R
|
||||
import cash.z.ecc.android.ZcashWalletApp
|
||||
import cash.z.ecc.android.databinding.FragmentBackupBinding
|
||||
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.MetricType.SEED_PHRASE_LOADED
|
||||
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.twig
|
||||
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.util.AddressPartNumberSpan
|
||||
import cash.z.ecc.kotlin.mnemonic.Mnemonics
|
||||
|
@ -36,7 +37,7 @@ import kotlinx.coroutines.withContext
|
|||
class BackupFragment : BaseFragment<FragmentBackupBinding>() {
|
||||
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
|
||||
|
||||
|
@ -61,7 +62,7 @@ class BackupFragment : BaseFragment<FragmentBackupBinding>() {
|
|||
onEnterWallet().also { if (hasBackUp) tapped(BACKUP_DONE) else tapped(BACKUP_VERIFY) }
|
||||
}
|
||||
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) {
|
||||
super.onAttach(context)
|
||||
walletSetup.checkSeed().onEach {
|
||||
when(it) {
|
||||
SEED_WITH_BACKUP -> {
|
||||
hasBackUp = true
|
||||
}
|
||||
hasBackUp = when(it) {
|
||||
SEED_WITH_BACKUP -> true
|
||||
else -> false
|
||||
}
|
||||
}.launchIn(lifecycleScope)
|
||||
}
|
||||
|
@ -85,14 +85,14 @@ class BackupFragment : BaseFragment<FragmentBackupBinding>() {
|
|||
override fun onResume() {
|
||||
super.onResume()
|
||||
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
|
||||
private suspend fun calculateBirthday(): Int {
|
||||
var storedBirthday: Int = 0
|
||||
var oldestTransactionHeight:Int = 0
|
||||
var storedBirthday = 0
|
||||
var oldestTransactionHeight = 0
|
||||
try {
|
||||
storedBirthday = walletSetup.loadBirthdayHeight() ?: 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) {
|
||||
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()
|
||||
}
|
||||
|
@ -131,7 +131,7 @@ class BackupFragment : BaseFragment<FragmentBackupBinding>() {
|
|||
mainActivity!!.feedback.measure(SEED_PHRASE_LOADED) {
|
||||
val lockBox = LockBox(ZcashWalletApp.instance)
|
||||
val mnemonics = Mnemonics()
|
||||
val seedPhrase = lockBox.getCharsUtf8(LockBoxKey.SEED_PHRASE)!!
|
||||
val seedPhrase = lockBox.getCharsUtf8(Const.Backup.SEED_PHRASE)!!
|
||||
val result = mnemonics.toWordList(seedPhrase)
|
||||
result
|
||||
}
|
||||
|
|
|
@ -4,10 +4,13 @@ import android.content.Context
|
|||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import cash.z.ecc.android.R
|
||||
import cash.z.ecc.android.databinding.FragmentLandingBinding
|
||||
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.Funnel.Restore
|
||||
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.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import java.lang.IllegalStateException
|
||||
|
||||
class LandingFragment : BaseFragment<FragmentLandingBinding>() {
|
||||
override val screen = Report.Screen.LANDING
|
||||
|
@ -32,14 +36,14 @@ class LandingFragment : BaseFragment<FragmentLandingBinding>() {
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding.buttonPositive.setOnClickListener {
|
||||
when (binding.buttonPositive.text.toString().toLowerCase()) {
|
||||
"new" -> onNewWallet().also { tapped(LANDING_NEW) }
|
||||
"backup" -> onBackupWallet().also { tapped(LANDING_BACKUP) }
|
||||
when (binding.buttonPositive.text.toString().toLowerCase(locale())) {
|
||||
R.string.landing_button_primary.toAppString(true) -> onNewWallet().also { tapped(LANDING_NEW) }
|
||||
R.string.landing_button_primary_create_success.toAppString(true) -> onBackupWallet().also { tapped(LANDING_BACKUP) }
|
||||
}
|
||||
}
|
||||
binding.buttonNegative.setOnLongClickListener {
|
||||
tapped(DEVELOPER_WALLET_PROMPT)
|
||||
if (binding.buttonNegative.text.toString().toLowerCase() == "restore") {
|
||||
if (binding.buttonNegative.text.toString().toLowerCase(locale()) == "restore") {
|
||||
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.")
|
||||
.setTitle("Import Dev Wallet?")
|
||||
|
@ -55,12 +59,13 @@ class LandingFragment : BaseFragment<FragmentLandingBinding>() {
|
|||
}
|
||||
.show()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
false
|
||||
}
|
||||
binding.buttonNegative.setOnClickListener {
|
||||
when (binding.buttonNegative.text.toString().toLowerCase()) {
|
||||
"restore" -> onRestoreWallet().also {
|
||||
when (binding.buttonNegative.text.toString().toLowerCase(locale())) {
|
||||
R.string.landing_button_secondary.toAppString(true) -> onRestoreWallet().also {
|
||||
mainActivity?.reportFunnel(Restore.Initiated)
|
||||
tapped(LANDING_RESTORE)
|
||||
}
|
||||
|
@ -76,6 +81,7 @@ class LandingFragment : BaseFragment<FragmentLandingBinding>() {
|
|||
SEED_WITHOUT_BACKUP, SEED_WITH_BACKUP -> {
|
||||
mainActivity?.safeNavigate(R.id.nav_backup)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}.launchIn(lifecycleScope)
|
||||
}
|
||||
|
@ -89,15 +95,13 @@ class LandingFragment : BaseFragment<FragmentLandingBinding>() {
|
|||
when (count) {
|
||||
1 -> {
|
||||
tapped(LANDING_BACKUP_SKIPPED_1)
|
||||
binding.textMessage.text =
|
||||
"Are you sure? Without a backup, funds can be lost FOREVER!"
|
||||
binding.buttonNegative.text = "Later"
|
||||
binding.textMessage.setText(R.string.landing_backup_skipped_message_1)
|
||||
binding.buttonNegative.setText(R.string.landing_button_backup_skipped_1)
|
||||
}
|
||||
2 -> {
|
||||
tapped(LANDING_BACKUP_SKIPPED_2)
|
||||
binding.textMessage.text =
|
||||
"You can't backup later. You're probably going to lose your funds!"
|
||||
binding.buttonNegative.text = "I've been warned"
|
||||
binding.textMessage.setText(R.string.landing_backup_skipped_message_2)
|
||||
binding.buttonNegative.setText(R.string.landing_button_backup_skipped_2)
|
||||
}
|
||||
else -> {
|
||||
tapped(LANDING_BACKUP_SKIPPED_3)
|
||||
|
@ -113,15 +117,15 @@ class LandingFragment : BaseFragment<FragmentLandingBinding>() {
|
|||
// AKA import wallet
|
||||
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 birthday = 663174//626599
|
||||
val birthday = 663174 //991645
|
||||
mainActivity?.apply {
|
||||
lifecycleScope.launch {
|
||||
mainActivity?.startSync(walletSetup.importWallet(seedPhrase, birthday))
|
||||
}
|
||||
binding.buttonPositive.isEnabled = true
|
||||
binding.textMessage.text = "Wallet imported! Congratulations!"
|
||||
binding.buttonNegative.text = "Skip"
|
||||
binding.buttonPositive.text = "Backup"
|
||||
binding.textMessage.setText(R.string.landing_import_success_message)
|
||||
binding.buttonNegative.setText(R.string.landing_button_secondary_import_success)
|
||||
binding.buttonPositive.setText(R.string.landing_import_success_primary_button)
|
||||
playSound("sound_receive_small.mp3")
|
||||
vibrateSuccess()
|
||||
}
|
||||
|
@ -129,18 +133,27 @@ class LandingFragment : BaseFragment<FragmentLandingBinding>() {
|
|||
|
||||
private fun onNewWallet() {
|
||||
lifecycleScope.launch {
|
||||
val ogText = binding.buttonPositive.text
|
||||
binding.buttonPositive.text = "creating"
|
||||
binding.buttonPositive.setText(R.string.landing_button_progress_create)
|
||||
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.textMessage.text = "Wallet created! Congratulations!"
|
||||
binding.buttonNegative.text = "Skip"
|
||||
binding.buttonPositive.text = "Backup"
|
||||
mainActivity?.playSound("sound_receive_small.mp3")
|
||||
mainActivity?.vibrateSuccess()
|
||||
binding.buttonPositive.isEnabled = true
|
||||
binding.textMessage.setText(R.string.landing_create_success_message)
|
||||
binding.buttonNegative.setText(R.string.landing_button_secondary_create_success)
|
||||
binding.buttonPositive.setText(R.string.landing_button_primary_create_success)
|
||||
mainActivity?.playSound("sound_receive_small.mp3")
|
||||
mainActivity?.vibrateSuccess()
|
||||
} catch (t: Throwable) {
|
||||
Toast.makeText(context, "Failed to create wallet", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,24 +1,20 @@
|
|||
package cash.z.ecc.android.ui.setup
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import cash.z.ecc.android.ZcashWalletApp
|
||||
import cash.z.ecc.android.ext.Const
|
||||
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.sdk.Initializer
|
||||
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.WalletBirthdayTool
|
||||
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.*
|
||||
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.single
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
|
@ -39,152 +35,17 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() {
|
|||
lateinit var feedback: Feedback
|
||||
|
||||
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 {
|
||||
when {
|
||||
lockBox.getBoolean(LockBoxKey.HAS_BACKUP) -> emit(SEED_WITH_BACKUP)
|
||||
lockBox.getBoolean(LockBoxKey.HAS_SEED) -> emit(SEED_WITHOUT_BACKUP)
|
||||
lockBox.getBoolean(Const.Backup.HAS_BACKUP) -> emit(SEED_WITH_BACKUP)
|
||||
lockBox.getBoolean(Const.Backup.HAS_SEED) -> emit(SEED_WITHOUT_BACKUP)
|
||||
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.
|
||||
*/
|
||||
|
@ -192,15 +53,94 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() {
|
|||
mnemonics.validate(seedPhrase.toCharArray())
|
||||
}
|
||||
|
||||
object LockBoxKey {
|
||||
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"
|
||||
fun loadBirthdayHeight(): Int? {
|
||||
val h: Int? = lockBox[Const.Backup.BIRTHDAY_HEIGHT]
|
||||
twig("Loaded birthday with key ${Const.Backup.BIRTHDAY_HEIGHT} and found $h")
|
||||
return h
|
||||
}
|
||||
|
||||
suspend fun newWallet(): Initializer {
|
||||
twig("Initializing new wallet")
|
||||
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
|
||||
}
|
||||
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,8 +15,8 @@
|
|||
android:centerX="54"
|
||||
android:centerY="36.01165"
|
||||
android:type="radial">
|
||||
<item android:offset="0" android:color="#FF3F3F4F"/>
|
||||
<item android:offset="1" android:color="#FF000000"/>
|
||||
<item android:offset="0" android:color="@color/app_icon_background_0"/>
|
||||
<item android:offset="1" android:color="@color/app_icon_background_1"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
<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:strokeWidth="1"
|
||||
android:fillColor="#FFB900"
|
||||
android:fillColor="@color/app_icon_foreground"
|
||||
android:fillType="nonZero"
|
||||
android:strokeColor="#00000000"/>
|
||||
<path
|
||||
|
@ -90,13 +90,13 @@
|
|||
<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:strokeWidth="1"
|
||||
android:fillColor="#FFB900"
|
||||
android:fillColor="@color/app_icon_foreground"
|
||||
android:fillType="nonZero"
|
||||
android:strokeColor="#00000000"/>
|
||||
<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:strokeWidth="1"
|
||||
android:fillColor="#FFB900"
|
||||
android:fillColor="@color/app_icon_foreground"
|
||||
android:fillType="nonZero"
|
||||
android:strokeColor="#00000000"/>
|
||||
</vector>
|
||||
|
|
|
@ -126,7 +126,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
tools:text="fitness"
|
||||
app:layout_constraintBottom_toTopOf="@+id/text_address_part_5"
|
||||
app:layout_constraintStart_toEndOf="@id/barrier_left_address_column_1"
|
||||
|
@ -218,7 +218,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
tools:text="goals"
|
||||
app:layout_constraintBottom_toTopOf="@+id/text_address_part_6"
|
||||
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_height="wrap_content"
|
||||
tools:text="Birthday Height: 510,123"
|
||||
android:textSize="20dp"
|
||||
android:textSize="20sp"
|
||||
android:fontFamily="@font/inconsolata"
|
||||
app:layout_constraintTop_toBottomOf="@id/receive_address_parts"
|
||||
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"
|
||||
style="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:gravity="center"
|
||||
android:text="empowering\neveryone\nwith\neconomic\nfreedom"
|
||||
android:text="@string/backup_slogan"
|
||||
android:textColor="@color/zcashWhite_50"
|
||||
android:textSize="56dp"
|
||||
android:alpha="0.03"
|
||||
app:layout_constraintBottom_toBottomOf="@id/icon_logo"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/icon_logo" />
|
||||
app:layout_constraintTop_toTopOf="@id/icon_logo"
|
||||
tools:ignore="SpUsage" />
|
||||
|
||||
<ImageView
|
||||
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_constraintWidth_percent="0.4053398058"
|
||||
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 -->
|
||||
<TextView
|
||||
|
@ -367,7 +369,7 @@ text_address_part_3, text_address_part_6, text_address_part_9, text_address_part
|
|||
android:gravity="center"
|
||||
android:paddingStart="32dp"
|
||||
android:paddingEnd="32dp"
|
||||
android:text="Store these backup words securely."
|
||||
android:text="@string/backup_instruction_store_words"
|
||||
android:textColor="@color/zcashWhite"
|
||||
app:layout_constraintBottom_toTopOf="@id/guideline_buttons"
|
||||
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:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Verify"
|
||||
android:text="@string/backup_button_primary"
|
||||
android:textColor="@color/text_dark"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
|
|
@ -54,7 +54,8 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
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
|
||||
android:id="@+id/back_button_hit_area"
|
||||
|
@ -66,14 +67,15 @@
|
|||
app:layout_constraintHorizontal_bias="0.01"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.045" />
|
||||
app:layout_constraintVertical_bias="0.045"
|
||||
android:focusable="true" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_balance_available"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Updating"
|
||||
android:text="@string/history_balance_updating"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:textColor="@color/text_light"
|
||||
android:visibility="visible"
|
||||
|
@ -87,8 +89,8 @@
|
|||
android:id="@+id/label_balance"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="4dp"
|
||||
android:text="Available"
|
||||
android:layout_marginStart="4dp"
|
||||
android:text="@string/history_balance_available"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:textColor="@color/colorPrimary"
|
||||
app:layout_constraintBaseline_toBaselineOf="@id/text_balance_available"
|
||||
|
@ -99,7 +101,7 @@
|
|||
android:id="@+id/text_balance_description"
|
||||
android:layout_width="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:textColor="@color/text_light_dimmed"
|
||||
app:layout_constraintBottom_toBottomOf="@id/back_button"
|
||||
|
@ -124,9 +126,9 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:text="Your Wallet History"
|
||||
android:text="@string/history_header_transactions"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="22dp"
|
||||
android:textSize="22sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
@ -135,7 +137,8 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="Shielded address: "
|
||||
android:paddingEnd="8dp"
|
||||
android:text="@string/history_address_label"
|
||||
android:textColor="@color/text_light_dimmed"
|
||||
app:layout_constraintStart_toStartOf="@id/text_header_title"
|
||||
app:layout_constraintTop_toBottomOf="@+id/text_header_title" />
|
||||
|
@ -160,7 +163,8 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.15"
|
||||
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>
|
||||
|
||||
<View
|
||||
|
@ -176,9 +180,9 @@
|
|||
android:id="@+id/empty_message"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="No history yet."
|
||||
android:text="@string/history_empty_text"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="18dp"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
android:id="@+id/text_balance_available"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Updating"
|
||||
android:text="@string/home_balance_updating"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:textColor="@color/text_light"
|
||||
android:visibility="visible"
|
||||
|
@ -59,8 +59,8 @@
|
|||
android:id="@+id/label_balance"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="4dp"
|
||||
android:text="Available"
|
||||
android:layout_marginStart="4dp"
|
||||
android:text="@string/home_balance_available"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:textColor="@color/colorPrimary"
|
||||
android:visibility="gone"
|
||||
|
@ -72,7 +72,7 @@
|
|||
android:id="@+id/text_balance_description"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="(enter an amount to send)"
|
||||
android:text="@string/home_instruction_enter_amount"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
|
||||
|
@ -97,7 +97,8 @@
|
|||
app:layout_constraintStart_toStartOf="@id/guide_keys"
|
||||
app:layout_constraintTop_toTopOf="@id/guide_keys"
|
||||
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
|
||||
android:id="@+id/button_number_pad_2"
|
||||
|
@ -111,7 +112,8 @@
|
|||
app:layout_constraintStart_toStartOf="@id/guide_keys"
|
||||
app:layout_constraintTop_toTopOf="@id/guide_keys"
|
||||
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
|
||||
android:id="@+id/button_number_pad_3"
|
||||
|
@ -124,7 +126,8 @@
|
|||
app:layout_constraintEnd_toEndOf="@id/guide_keys"
|
||||
app:layout_constraintTop_toTopOf="@id/guide_keys"
|
||||
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
|
||||
android:id="@+id/button_number_pad_4"
|
||||
|
@ -137,7 +140,8 @@
|
|||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toStartOf="@id/guide_keys"
|
||||
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
|
||||
android:id="@+id/button_number_pad_5"
|
||||
|
@ -150,7 +154,8 @@
|
|||
app:layout_constraintEnd_toEndOf="@id/guide_keys"
|
||||
app:layout_constraintStart_toStartOf="@id/guide_keys"
|
||||
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
|
||||
android:id="@+id/button_number_pad_6"
|
||||
|
@ -162,7 +167,8 @@
|
|||
app:layout_constraintDimensionRatio="H,1:1"
|
||||
app:layout_constraintEnd_toEndOf="@id/guide_keys"
|
||||
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
|
||||
android:id="@+id/button_number_pad_7"
|
||||
|
@ -175,7 +181,8 @@
|
|||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toStartOf="@id/guide_keys"
|
||||
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
|
||||
android:id="@+id/button_number_pad_8"
|
||||
|
@ -188,7 +195,8 @@
|
|||
app:layout_constraintEnd_toEndOf="@id/guide_keys"
|
||||
app:layout_constraintStart_toStartOf="@id/guide_keys"
|
||||
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
|
||||
android:id="@+id/button_number_pad_9"
|
||||
|
@ -200,7 +208,8 @@
|
|||
app:layout_constraintDimensionRatio="H,1:1"
|
||||
app:layout_constraintEnd_toEndOf="@id/guide_keys"
|
||||
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
|
||||
android:id="@+id/button_number_pad_decimal"
|
||||
|
@ -208,7 +217,7 @@
|
|||
android:layout_height="0dp"
|
||||
style="@style/Zcash.TextView.NumberPad"
|
||||
android:paddingBottom="2dp"
|
||||
android:text="."
|
||||
android:text="@string/key_decimal"
|
||||
app:layout_constraintBottom_toBottomOf="@id/guide_keys"
|
||||
app:layout_constraintDimensionRatio="H,1:1"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
|
@ -227,14 +236,15 @@
|
|||
app:layout_constraintEnd_toEndOf="@id/guide_keys"
|
||||
app:layout_constraintStart_toStartOf="@id/guide_keys"
|
||||
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
|
||||
android:id="@+id/button_number_pad_back"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
style="@style/Zcash.TextView.NumberPad"
|
||||
android:text="<"
|
||||
android:text="@string/key_backspace"
|
||||
app:layout_constraintBottom_toBottomOf="@id/guide_keys"
|
||||
app:layout_constraintDimensionRatio="H,1:1"
|
||||
app:layout_constraintEnd_toEndOf="@id/guide_keys"
|
||||
|
@ -248,7 +258,8 @@
|
|||
android:clickable="true"
|
||||
android:background="#D0000000"
|
||||
tools:visibility="gone"
|
||||
android:elevation="5dp" />
|
||||
android:elevation="5dp"
|
||||
android:focusable="true" />
|
||||
|
||||
<!-- -->
|
||||
<!-- Upper Layer -->
|
||||
|
@ -284,7 +295,7 @@
|
|||
android:paddingStart="56dp"
|
||||
android:paddingEnd="56dp"
|
||||
android:textColor="@color/selector_button_text_dark"
|
||||
android:textSize="16dp"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintTop_toTopOf="@id/lottie_button_loading"
|
||||
app:layout_constraintBottom_toBottomOf="@id/lottie_button_loading"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
@ -304,7 +315,8 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/icon_profile"
|
||||
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
|
||||
android:id="@+id/icon_profile"
|
||||
|
@ -320,7 +332,8 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.064"
|
||||
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
|
||||
android:id="@+id/hit_area_receive"
|
||||
|
@ -385,7 +398,7 @@
|
|||
android:includeFontPadding="false"
|
||||
tools:text="$0"
|
||||
android:textAppearance="@style/Zcash.TextAppearance.Zec"
|
||||
android:textSize="72dp"
|
||||
android:textSize="72sp"
|
||||
android:maxLines="1"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
|
@ -408,8 +421,9 @@
|
|||
android:background="@drawable/background_banner"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingTop="8dp"
|
||||
android:text="No Balance"
|
||||
android:text="@string/home_no_balance"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:textColor="@color/text_light"
|
||||
app:layout_constraintEnd_toEndOf="@id/icon_profile"
|
||||
|
@ -422,7 +436,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="Fund Now"
|
||||
android:text="@string/home_instruction_fund_now"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:textColor="@color/colorPrimary"
|
||||
app:layout_constraintBaseline_toBaselineOf="@id/text_banner_message"
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
android:gravity="center"
|
||||
android:paddingStart="32dp"
|
||||
android:paddingEnd="32dp"
|
||||
android:text="Welcome to the ECC Wallet!"
|
||||
android:text="@string/landing_title"
|
||||
android:textColor="@color/zcashWhite"
|
||||
app:layout_constraintBottom_toTopOf="@id/guideline_buttons"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -61,7 +61,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Zcash.Button.OutlinedButton"
|
||||
android:text="Restore"
|
||||
android:text="@string/landing_button_secondary"
|
||||
android:textColor="@color/text_light"
|
||||
app:layout_constraintEnd_toStartOf="@id/button_positive"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
|
@ -73,7 +73,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:text="New"
|
||||
android:text="@string/landing_button_primary"
|
||||
android:textColor="@color/text_dark"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/button_negative"
|
||||
|
|
|
@ -62,7 +62,8 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.065"
|
||||
app:layout_constraintWidth_percent="0.08"
|
||||
app:srcCompat="@drawable/ic_cancel" />
|
||||
app:srcCompat="@drawable/ic_cancel"
|
||||
android:contentDescription="@string/content_description_profile_back" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon_settings"
|
||||
|
@ -78,7 +79,8 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.065"
|
||||
app:layout_constraintWidth_percent="0.08"
|
||||
app:srcCompat="@drawable/ic_settings" />
|
||||
app:srcCompat="@drawable/ic_settings"
|
||||
android:contentDescription="@string/content_description_profile_settings" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon_profile"
|
||||
|
@ -92,7 +94,8 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.212"
|
||||
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
|
||||
android:id="@+id/hit_area_settings"
|
||||
|
@ -138,7 +141,8 @@
|
|||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.15"
|
||||
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
|
||||
android:id="@+id/hit_area_address"
|
||||
|
@ -214,7 +218,8 @@
|
|||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@id/guideline_content_end"
|
||||
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
|
||||
android:id="@+id/text_version"
|
||||
|
@ -222,7 +227,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:elevation="6dp"
|
||||
android:text="@string/profile_app_version"
|
||||
tools:text="@string/profile_app_version"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:textColor="@color/text_light_dimmed"
|
||||
app:layout_constraintBaseline_toBaselineOf="@id/text_banner_message"
|
||||
|
|
|
@ -39,30 +39,29 @@
|
|||
tools:background="@color/zcashRed" />
|
||||
|
||||
|
||||
<!-- <ImageView-->
|
||||
<!-- android:id="@+id/scan_frame"-->
|
||||
<!-- android:layout_width="0dp"-->
|
||||
<!-- android:layout_height="0dp"-->
|
||||
<!-- android:scaleType="centerCrop"-->
|
||||
<!-- android:src="@drawable/ic_scan_frame"-->
|
||||
<!-- app:layout_constraintDimensionRatio="H,1:1"-->
|
||||
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
|
||||
<!-- app:layout_constraintEnd_toStartOf="@id/spacer_bottom_right"-->
|
||||
<!-- app:layout_constraintStart_toEndOf="@id/spacer_bottom_left"-->
|
||||
<!-- app:layout_constraintTop_toTopOf="parent"-->
|
||||
<!-- app:layout_constraintVertical_bias="0.3" />-->
|
||||
|
||||
|
||||
<!-- <ImageView-->
|
||||
<!-- android:id="@+id/scan_frame"-->
|
||||
<!-- android:layout_width="0dp"-->
|
||||
<!-- android:layout_height="0dp"-->
|
||||
<!-- android:scaleType="centerCrop"-->
|
||||
<!-- android:src="@drawable/ic_scan_frame"-->
|
||||
<!-- app:layout_constraintDimensionRatio="H,1:1"-->
|
||||
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
|
||||
<!-- app:layout_constraintEnd_toStartOf="@id/spacer_bottom_right"-->
|
||||
<!-- app:layout_constraintStart_toEndOf="@id/spacer_bottom_left"-->
|
||||
<!-- app:layout_constraintTop_toTopOf="parent"-->
|
||||
<!-- app:layout_constraintVertical_bias="0.3" />-->
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/background_qr"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:contentDescription="@string/content_description_receive_qr_background"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_background_qr"
|
||||
app:layout_constraintDimensionRatio="H,1:1"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="H,1:1"
|
||||
app:layout_constraintEnd_toStartOf="@id/spacer_bottom_right"
|
||||
app:layout_constraintStart_toEndOf="@id/spacer_bottom_left"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
@ -73,21 +72,23 @@
|
|||
android:id="@+id/receive_qr_code"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:contentDescription="@string/content_description_receive_qr_code"
|
||||
android:onClick="copyAddress"
|
||||
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"
|
||||
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
|
||||
android:id="@+id/icon_qr_logo"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:contentDescription="@string/content_description_receive_qr_logo"
|
||||
android:onClick="copyAddress"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_shield_address"
|
||||
|
@ -102,12 +103,12 @@
|
|||
android:id="@+id/receive_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/address_label_shielded"
|
||||
android:drawableEnd="@drawable/ic_content_copy"
|
||||
android:drawablePadding="16dp"
|
||||
android:text="@string/receive_address_label_shielded"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="20dp"
|
||||
android:textSize="20sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/spacer_title" />
|
||||
|
@ -119,10 +120,10 @@
|
|||
android:layout_height="0dp"
|
||||
android:layout_marginTop="24dp"
|
||||
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_constraintEnd_toEndOf="@id/background_qr"
|
||||
app:layout_constraintStart_toStartOf="@id/background_qr"
|
||||
app:layout_constraintTop_toBottomOf="@id/background_qr"
|
||||
tools:background="@color/spacer" />
|
||||
|
||||
<!-- Back Button -->
|
||||
|
@ -132,14 +133,15 @@
|
|||
android:layout_height="0dp"
|
||||
android:tint="@color/text_light_dimmed"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="H,1:1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.088"
|
||||
app:layout_constraintStart_toStartOf="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:srcCompat="@drawable/ic_cancel" />
|
||||
app:layout_constraintWidth_percent="0.08"
|
||||
app:srcCompat="@drawable/ic_cancel"
|
||||
android:contentDescription="@string/content_description_receive_back" />
|
||||
|
||||
<View
|
||||
android:id="@+id/back_button_hit_area"
|
||||
|
@ -147,6 +149,7 @@
|
|||
android:layout_height="68dp"
|
||||
android:layout_marginStart="24dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
@ -157,17 +160,16 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/TextAppearance.MaterialComponents.Headline6"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:maxLines="1"
|
||||
android:text="@string/receive_address_title"
|
||||
android:textColor="@color/text_light"
|
||||
app:autoSizeTextType="uniform"
|
||||
app:layout_constraintBottom_toBottomOf="@id/back_button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/back_button_hit_area"
|
||||
app:layout_constraintTop_toTopOf="@id/back_button" />
|
||||
|
||||
|
||||
|
||||
<!-- 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 -->
|
||||
|
@ -180,95 +182,95 @@
|
|||
app:layout_constraintEnd_toEndOf="@id/receive_title"
|
||||
app:layout_constraintStart_toStartOf="@id/receive_title"
|
||||
app:layout_constraintTop_toBottomOf="@id/receive_title">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_address_part_1"
|
||||
style="@style/Zcash.TextAppearance.AddressPart"
|
||||
android:layout_width="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_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.0"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text=" ztestsaplin" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_address_part_3"
|
||||
style="@style/Zcash.TextAppearance.AddressPart"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text=" jceuu9s2p6t"
|
||||
style="@style/Zcash.TextAppearance.AddressPart"
|
||||
app:layout_constraintBottom_toTopOf="@+id/text_address_part_5"
|
||||
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
|
||||
android:id="@+id/text_address_part_5"
|
||||
style="@style/Zcash.TextAppearance.AddressPart"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text=" 7u7uarqls7d"
|
||||
style="@style/Zcash.TextAppearance.AddressPart"
|
||||
app:layout_constraintBottom_toTopOf="@+id/text_address_part_7"
|
||||
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
|
||||
android:id="@+id/text_address_part_7"
|
||||
style="@style/Zcash.TextAppearance.AddressPart"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text=" rzq85xggu56"
|
||||
style="@style/Zcash.TextAppearance.AddressPart"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
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
|
||||
android:id="@+id/text_address_part_2"
|
||||
style="@style/Zcash.TextAppearance.AddressPart"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:text=" g1mwjzlg62j"
|
||||
style="@style/Zcash.TextAppearance.AddressPart"
|
||||
app:layout_constraintBottom_toTopOf="@+id/text_address_part_4"
|
||||
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
|
||||
android:id="@+id/text_address_part_4"
|
||||
style="@style/Zcash.TextAppearance.AddressPart"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text=" wns6qxwec6v"
|
||||
style="@style/Zcash.TextAppearance.AddressPart"
|
||||
app:layout_constraintBottom_toTopOf="@+id/text_address_part_6"
|
||||
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
|
||||
android:id="@+id/text_address_part_6"
|
||||
style="@style/Zcash.TextAppearance.AddressPart"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text=" gtg3tpgqxjd"
|
||||
style="@style/Zcash.TextAppearance.AddressPart"
|
||||
app:layout_constraintBottom_toTopOf="@+id/text_address_part_8"
|
||||
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
|
||||
android:id="@+id/text_address_part_8"
|
||||
style="@style/Zcash.TextAppearance.AddressPart"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text=" k904xderng6"
|
||||
style="@style/Zcash.TextAppearance.AddressPart"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
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
|
||||
android:id="@+id/barrier_left_address_column"
|
||||
|
|
|
@ -141,6 +141,7 @@
|
|||
android:id="@+id/scan_frame"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:contentDescription="@string/content_description_scan_frame"
|
||||
android:scaleType="centerCrop"
|
||||
android:src="@drawable/ic_scan_frame"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
@ -155,13 +156,13 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:ellipsize="end"
|
||||
android:fontFamily="@font/inconsolata"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/text_light_dimmed"
|
||||
android:textSize="16dp"
|
||||
android:ellipsize="end"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingTop="8dp"
|
||||
android:textColor="@color/text_light_dimmed"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintEnd_toEndOf="@id/scan_frame"
|
||||
app:layout_constraintStart_toStartOf="@id/scan_frame"
|
||||
app:layout_constraintTop_toBottomOf="@id/scan_frame"
|
||||
|
@ -169,25 +170,28 @@
|
|||
|
||||
<ImageView
|
||||
android:id="@+id/back_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:tint="@color/text_light"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:contentDescription="@string/content_description_scan_back"
|
||||
android:tint="@color/text_light_dimmed"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="H,1:1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.05"
|
||||
app:layout_constraintHorizontal_bias="0.088"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
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
|
||||
android:id="@+id/back_button_hit_area"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_width="68dp"
|
||||
android:layout_height="68dp"
|
||||
android:layout_marginStart="24dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.01"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.045" />
|
||||
|
@ -197,10 +201,10 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/TextAppearance.MaterialComponents.Headline6"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:maxLines="1"
|
||||
android:text="@string/scan_address_title"
|
||||
android:textColor="@color/text_light"
|
||||
app:autoSizeTextType="uniform"
|
||||
app:layout_constraintBottom_toBottomOf="@id/back_button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/back_button_hit_area"
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
android:id="@+id/back_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/content_description_send_back"
|
||||
android:tint="@color/text_light"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -33,6 +34,7 @@
|
|||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.01"
|
||||
|
@ -52,7 +54,6 @@
|
|||
android:paddingEnd="16dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="0dp"
|
||||
android:text="$20.1"
|
||||
android:textAppearance="@style/Zcash.TextAppearance.Zec"
|
||||
android:textSize="200sp"
|
||||
app:autoSizeMaxTextSize="40sp"
|
||||
|
@ -62,14 +63,15 @@
|
|||
app:layout_constraintEnd_toStartOf="@id/button_send"
|
||||
app:layout_constraintStart_toEndOf="@id/spacer_title"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="$20.1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_banner_message_start"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="6dp"
|
||||
android:text="from your "
|
||||
android:text="@string/send_fund_source_prefix"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
|
||||
android:textColor="@color/text_light"
|
||||
app:layout_constraintBottom_toTopOf="@id/guideline_content_top"
|
||||
|
@ -83,7 +85,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="6dp"
|
||||
android:text="shielded"
|
||||
android:text="@string/send_fund_source_highlight"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
|
||||
android:textColor="@color/colorPrimary"
|
||||
app:layout_constraintBaseline_toBaselineOf="@id/text_banner_message_start"
|
||||
|
@ -95,7 +97,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="6dp"
|
||||
android:text=" wallet"
|
||||
android:text="@string/send_fund_source_suffix"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Caption"
|
||||
android:textColor="@color/text_light"
|
||||
app:layout_constraintBaseline_toBaselineOf="@id/text_banner_message_start"
|
||||
|
@ -107,6 +109,7 @@
|
|||
android:id="@+id/image_down_arrow"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/content_description_send_arrow"
|
||||
android:tint="@color/colorPrimary"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@id/text_banner_message_end"
|
||||
|
@ -127,11 +130,16 @@
|
|||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button_send"
|
||||
android:layout_width="74dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="38dp"
|
||||
android:text="Send"
|
||||
android:textColor="@color/text_dark"
|
||||
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_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.95"
|
||||
|
@ -143,16 +151,16 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
app:autoSizeMaxTextSize="12sp"
|
||||
app:autoSizeMinTextSize="6sp"
|
||||
app:autoSizeTextType="uniform"
|
||||
android:fontFamily="@font/inconsolata"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingStart="8dp"
|
||||
android:textColor="@color/zcashRed"
|
||||
android:maxLines="1"
|
||||
android:textSize="14sp"
|
||||
android:gravity="center"
|
||||
app:autoSizeMaxTextSize="12sp"
|
||||
app:autoSizeMinTextSize="6sp"
|
||||
app:autoSizeTextType="uniform"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/text_banner_message_start"
|
||||
|
@ -176,9 +184,9 @@
|
|||
android:id="@+id/spacer_lower_content"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHeight_percent="0.04"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/check_include_address" />
|
||||
|
||||
<!-- Input: Address -->
|
||||
|
@ -186,7 +194,7 @@
|
|||
android:id="@+id/text_layout_address"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="To"
|
||||
android:hint="@string/send_address_hint"
|
||||
android:theme="@style/Zcash.Overlay.TextInputLayout"
|
||||
app:endIconDrawable="@drawable/ic_qr_scan"
|
||||
app:endIconMode="custom"
|
||||
|
@ -215,7 +223,7 @@
|
|||
android:id="@+id/text_layout_memo"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Memo"
|
||||
android:hint="@string/send_memo_hint"
|
||||
android:theme="@style/Zcash.Overlay.TextInputLayout"
|
||||
app:endIconMode="clear_text"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -239,17 +247,17 @@
|
|||
tools:text="WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<!-- <ImageView-->
|
||||
<!-- android:id="@+id/clear_memo"-->
|
||||
<!-- android:layout_width="24dp"-->
|
||||
<!-- android:layout_height="24dp"-->
|
||||
<!-- android:layout_marginEnd="10dp"-->
|
||||
<!-- android:layout_marginTop="6dp"-->
|
||||
<!-- android:elevation="6dp"-->
|
||||
<!-- android:src="@drawable/ic_close_black_24dp"-->
|
||||
<!-- android:tint="@color/text_light"-->
|
||||
<!-- app:layout_constraintEnd_toEndOf="@id/text_layout_memo"-->
|
||||
<!-- app:layout_constraintTop_toTopOf="@id/text_layout_memo" />-->
|
||||
<!-- <ImageView-->
|
||||
<!-- android:id="@+id/clear_memo"-->
|
||||
<!-- android:layout_width="24dp"-->
|
||||
<!-- android:layout_height="24dp"-->
|
||||
<!-- android:layout_marginEnd="10dp"-->
|
||||
<!-- android:layout_marginTop="6dp"-->
|
||||
<!-- android:elevation="6dp"-->
|
||||
<!-- android:src="@drawable/ic_close_black_24dp"-->
|
||||
<!-- android:tint="@color/text_light"-->
|
||||
<!-- app:layout_constraintEnd_toEndOf="@id/text_layout_memo"-->
|
||||
<!-- app:layout_constraintTop_toTopOf="@id/text_layout_memo" />-->
|
||||
|
||||
<!-- Spacer to help position checkbox under the memo line -->
|
||||
<Space
|
||||
|
@ -270,7 +278,7 @@
|
|||
android:padding="0dp"
|
||||
android:scaleX="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:textColor="@color/text_light_dimmed"
|
||||
android:textSize="14sp"
|
||||
|
@ -298,7 +306,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="1dp"
|
||||
android:text="On clipboard"
|
||||
android:text="@string/send_history_clipboard"
|
||||
android:textAllCaps="true"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -320,6 +328,7 @@
|
|||
android:id="@+id/image_shield"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:contentDescription="@string/content_description_send_shield"
|
||||
android:src="@drawable/ic_shielded"
|
||||
android:tint="@color/colorPrimary"
|
||||
app:layout_constraintBottom_toBottomOf="@id/clipboard_address_label"
|
||||
|
@ -333,7 +342,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:text="Unknown"
|
||||
android:text="@string/unknown"
|
||||
android:textColor="@color/colorPrimary"
|
||||
app:layout_constraintBottom_toTopOf="@id/clipboard_address"
|
||||
app:layout_constraintStart_toEndOf="@id/image_shield"
|
||||
|
@ -344,16 +353,17 @@
|
|||
android:id="@+id/clipboard_address"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="zasdfaksfjaslfjaslfkjaslk;kfjaslkfjasld;kfjaslfjdasflja"
|
||||
android:textColor="@color/text_light"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
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
|
||||
android:id="@+id/image_clipboard_address_selected"
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:contentDescription="@string/content_description_send_selected"
|
||||
android:src="@drawable/ic_check_shielded"
|
||||
android:tint="@color/colorPrimary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
@ -383,7 +393,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="1dp"
|
||||
android:text="last used"
|
||||
android:text="@string/send_history_last"
|
||||
android:textAllCaps="true"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body2"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -403,6 +413,7 @@
|
|||
android:id="@+id/image_last_used_shield"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:contentDescription="@string/content_description_send_shield"
|
||||
android:src="@drawable/ic_shielded"
|
||||
android:tint="@color/colorPrimary"
|
||||
app:layout_constraintBottom_toBottomOf="@id/last_used_address_label"
|
||||
|
@ -417,7 +428,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Unknown"
|
||||
android:text="@string/unknown"
|
||||
android:textColor="@color/colorPrimary"
|
||||
app:layout_constraintBottom_toTopOf="@id/last_used_address"
|
||||
app:layout_constraintStart_toEndOf="@id/image_last_used_shield"
|
||||
|
@ -428,16 +439,17 @@
|
|||
android:id="@+id/last_used_address"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="zasdfaksfjaslfjaslfkjaslk;kfjaslkfjasld;kfjaslfjdasflja"
|
||||
android:textColor="@color/text_light"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
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
|
||||
android:id="@+id/image_last_used_address_selected"
|
||||
android:layout_width="22dp"
|
||||
android:layout_height="22dp"
|
||||
android:contentDescription="@string/content_description_send_selected"
|
||||
android:src="@drawable/ic_check_shielded"
|
||||
android:tint="@color/colorPrimary"
|
||||
android:visibility="gone"
|
||||
|
@ -467,6 +479,7 @@
|
|||
android:id="@+id/image_scan_qr"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/content_description_send_scan_qr"
|
||||
android:paddingBottom="24dp"
|
||||
android:paddingEnd="1dp"
|
||||
android:paddingStart="6dp"
|
||||
|
|
|
@ -25,36 +25,40 @@
|
|||
<!-- Back Button -->
|
||||
<ImageView
|
||||
android:id="@+id/back_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:contentDescription="@string/content_description_send_final_back"
|
||||
android:tint="@color/zcashBlack_87"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="H,1:1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.05"
|
||||
app:layout_constraintHorizontal_bias="0.088"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
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" />
|
||||
|
||||
<View
|
||||
android:id="@+id/back_button_hit_area"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_width="68dp"
|
||||
android:layout_height="68dp"
|
||||
android:layout_marginStart="24dp"
|
||||
android:clickable="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.01"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.045" />
|
||||
app:layout_constraintVertical_bias="0.045"
|
||||
android:focusable="true" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_confirmation"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/TextAppearance.MaterialComponents.Headline5"
|
||||
android:autoSizeTextType="uniform"
|
||||
app:autoSizeTextType="uniform"
|
||||
android:gravity="center"
|
||||
android:maxLines="3"
|
||||
android:paddingEnd="16dp"
|
||||
|
@ -87,15 +91,14 @@
|
|||
android:id="@+id/error_message"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Oops! Failed to send due to insufficient funds!"
|
||||
android:textColor="@color/text_dark"
|
||||
android:gravity="center"
|
||||
tools:text="Oops! Failed to send due to insufficient funds!"
|
||||
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_constraintStart_toStartOf="@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-->
|
||||
<!-- android:id="@+id/text_status"-->
|
||||
<!-- android:layout_width="wrap_content"-->
|
||||
|
@ -141,9 +144,9 @@
|
|||
android:layout_height="wrap_content"
|
||||
style="@style/Zcash.Button.OutlinedButton"
|
||||
android:padding="20dp"
|
||||
android:translationY="-6dp"
|
||||
android:text="@string/cancel"
|
||||
android:textColor="@color/text_dark"
|
||||
android:translationY="-6dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/button_secondary"
|
||||
app:layout_constraintEnd_toEndOf="@id/guide_keys"
|
||||
app:layout_constraintStart_toStartOf="@id/guide_keys"
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
android:id="@+id/icon_exit"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:contentDescription="@string/content_description_settings_back"
|
||||
android:elevation="6dp"
|
||||
android:tint="@color/text_light_dimmed"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
@ -47,10 +48,10 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/TextAppearance.MaterialComponents.Headline6"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:maxLines="1"
|
||||
android:text="@string/settings_change_lightwalletd_server"
|
||||
android:textColor="@color/text_light"
|
||||
app:autoSizeTextType="uniform"
|
||||
app:layout_constraintBottom_toBottomOf="@id/icon_exit"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/hit_area_exit"
|
||||
|
@ -138,17 +139,18 @@
|
|||
android:layout_height="match_parent"
|
||||
android:background="@color/zcashWhite_24"
|
||||
android:clickable="true"
|
||||
android:elevation="8dp"
|
||||
android:focusableInTouchMode="true"
|
||||
android:elevation="8dp" />
|
||||
android:focusable="true" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/loading_progress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
app:layout_constraintTop_toTopOf="@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
|
||||
android:id="@+id/group_loading"
|
||||
|
@ -157,5 +159,4 @@
|
|||
app:constraint_referenced_ids="loading_progress,loading_view" />
|
||||
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -71,6 +71,7 @@
|
|||
android:id="@+id/close_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/content_description_transaction_details_back"
|
||||
android:tint="@color/zcashWhite_40"
|
||||
app:layout_constraintBottom_toTopOf="@id/text_timestamp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
@ -84,6 +85,7 @@
|
|||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.01"
|
||||
|
@ -108,13 +110,13 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:autoSizeMaxTextSize="18sp"
|
||||
android:autoSizeMinTextSize="6dp"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:gravity="bottom"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/tx_text_light_dimmed"
|
||||
android:textSize="18sp"
|
||||
app:autoSizeMaxTextSize="18sp"
|
||||
app:autoSizeMinTextSize="6dp"
|
||||
app:autoSizeTextType="uniform"
|
||||
app:layout_constraintBaseline_toBaselineOf="@id/text_block_height"
|
||||
app:layout_constraintEnd_toStartOf="@id/text_block_height_prefix"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
|
@ -204,7 +206,8 @@
|
|||
app:layout_constraintEnd_toEndOf="@id/top_box_icon_background"
|
||||
app:layout_constraintHeight_percent="0.0408"
|
||||
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
|
||||
android:id="@+id/top_box_label"
|
||||
|
@ -294,7 +297,8 @@
|
|||
app:layout_constraintEnd_toEndOf="@id/space_spots_memo"
|
||||
app:layout_constraintStart_toStartOf="@id/space_spots_memo"
|
||||
app:layout_constraintTop_toTopOf="@id/subway_label_memo"
|
||||
tools:visibility="visible" />
|
||||
tools:visibility="visible"
|
||||
android:contentDescription="@string/content_description_transaction_details_memo" />
|
||||
|
||||
<View
|
||||
android:id="@+id/subway_spot_address"
|
||||
|
@ -332,8 +336,9 @@
|
|||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:textColor="@color/tx_text_light_dimmed"
|
||||
android:textSize="18dp"
|
||||
android:textSize="18sp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
app:layout_constraintStart_toStartOf="@id/subway_line"
|
||||
app:layout_constraintTop_toTopOf="@id/subway_line"
|
||||
tools:text="+0.0001 network fee" />
|
||||
|
@ -346,7 +351,7 @@
|
|||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:textColor="@color/tx_text_light_dimmed"
|
||||
android:textSize="18dp"
|
||||
android:textSize="18sp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toStartOf="@id/subway_line"
|
||||
app:layout_constraintTop_toBottomOf="@id/subway_label_fee"
|
||||
|
@ -359,8 +364,9 @@
|
|||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingStart="8dp"
|
||||
android:text="@string/transaction_with_memo"
|
||||
android:textSize="18dp"
|
||||
android:textSize="18sp"
|
||||
android:visibility="invisible"
|
||||
app:layout_constraintStart_toStartOf="@id/subway_line"
|
||||
app:layout_constraintTop_toBottomOf="@id/subway_label_source"
|
||||
|
@ -378,7 +384,7 @@
|
|||
android:maxLines="3"
|
||||
android:scrollbars="vertical"
|
||||
android:textColor="@color/tx_text_light_dimmed"
|
||||
android:textSize="18dp"
|
||||
android:textSize="18sp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="@id/guideline_keyline_end"
|
||||
app:layout_constraintStart_toStartOf="@id/subway_line"
|
||||
|
@ -394,7 +400,8 @@
|
|||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="@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
|
||||
android:id="@+id/hit_area_memo_subway"
|
||||
|
@ -429,7 +436,7 @@
|
|||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="18dp"
|
||||
android:textSize="18sp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintStart_toStartOf="@id/subway_line"
|
||||
app:layout_constraintTop_toBottomOf="@id/subway_label_memo"
|
||||
|
@ -442,7 +449,7 @@
|
|||
android:layout_marginBottom="24dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:textColor="@color/tx_text_light_dimmed"
|
||||
android:textSize="18dp"
|
||||
android:textSize="18sp"
|
||||
app:layout_constraintBottom_toTopOf="@id/bottom_box_background"
|
||||
app:layout_constraintStart_toStartOf="@id/subway_line"
|
||||
tools:text="confirmed" />
|
||||
|
@ -477,7 +484,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:text="Total Spent"
|
||||
android:text="@string/transaction_details_total"
|
||||
android:textColor="@color/tx_primary"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toTopOf="@id/bottom_box_value"
|
||||
|
@ -495,7 +502,7 @@
|
|||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:maxLines="1"
|
||||
android:text="$4.32"
|
||||
tools:text="$4.32"
|
||||
android:textAppearance="@style/Zcash.TextAppearance.Zec"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="36sp"
|
||||
|
@ -519,7 +526,7 @@
|
|||
style="@style/Zcash.Button.OutlinedButton"
|
||||
android:gravity="center"
|
||||
android:padding="12dp"
|
||||
android:text="Explore"
|
||||
android:text="@string/transaction_details_button_explore"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:textColor="@color/tx_text_light_dimmed_less"
|
||||
app:icon="@drawable/ic_baseline_launch_24"
|
||||
|
@ -542,6 +549,6 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
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>
|
|
@ -27,26 +27,26 @@
|
|||
android:layout_width="1dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@color/text_light_dimmed"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<View
|
||||
android:id="@+id/divider_right"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@color/text_light_dimmed"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
<View
|
||||
android:id="@+id/indicator"
|
||||
android:layout_width="3dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/background_indicator_inbound"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
|
@ -59,6 +59,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:contentDescription="@string/content_description_transaction_shield"
|
||||
android:src="@drawable/ic_check_shield"
|
||||
app:layout_constraintBottom_toTopOf="@id/text_transaction_bottom"
|
||||
app:layout_constraintStart_toEndOf="@id/indicator"
|
||||
|
@ -76,20 +77,21 @@
|
|||
app:layout_constraintBottom_toTopOf="@id/text_transaction_bottom"
|
||||
app:layout_constraintStart_toEndOf="@id/indicator"
|
||||
app:layout_constraintTop_toTopOf="@id/indicator"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
android:importantForAccessibility="no"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_transaction_top"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="14sp"
|
||||
android:maxLines="1"
|
||||
android:autoSizeTextType="uniform"
|
||||
app:autoSizeTextType="uniform"
|
||||
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_constraintStart_toEndOf="@id/image_shield"
|
||||
app:layout_constraintTop_toTopOf="@id/image_shield_invisible"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
app:layout_goneMarginStart="16dp"
|
||||
|
@ -104,26 +106,26 @@
|
|||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
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:textSize="12sp"
|
||||
app:autoSizeTextType="uniform"
|
||||
app:layout_constraintBottom_toBottomOf="@id/indicator"
|
||||
app:layout_constraintEnd_toStartOf="@id/text_transaction_amount"
|
||||
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
|
||||
android:id="@+id/text_transaction_amount"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:paddingStart="12dp"
|
||||
android:autoSizeTextType="uniform"
|
||||
android:gravity="center_vertical"
|
||||
android:maxLines="1"
|
||||
android:paddingStart="12dp"
|
||||
android:textColor="@color/colorPrimary"
|
||||
android:textSize="20sp"
|
||||
app:autoSizeTextType="uniform"
|
||||
app:layout_constraintBottom_toBottomOf="@id/indicator"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/indicator"
|
||||
|
|
|
@ -90,11 +90,6 @@
|
|||
android:id="@+id/nav_send"
|
||||
android:name="cash.z.ecc.android.ui.send.SendFragment"
|
||||
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
|
||||
android:id="@+id/action_nav_send_to_nav_scan"
|
||||
app:destination="@id/nav_scan" />
|
||||
|
@ -156,6 +151,11 @@
|
|||
app:destination="@id/nav_history"
|
||||
app:popUpTo="@id/nav_home"
|
||||
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>
|
||||
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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
|
||||
|
||||
-->
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
|
||||
|
||||
-->
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -57,6 +57,9 @@
|
|||
<!-- every color here should be a reference to a palette color
|
||||
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_stroke">#282828</color>
|
||||
<color name="scan_overlay_background">@color/zcashBlack_87</color>
|
||||
|
|
|
@ -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>
|
|
@ -1,71 +1,266 @@
|
|||
<resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Common -->
|
||||
<string name="done">Done</string>
|
||||
<string name="done" tools:ignore="MissingTranslation">@string/translated_button_done</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>
|
||||
|
||||
<!-- 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>
|
||||
<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>
|
||||
<!-- -->
|
||||
<!-- SCREENS -->
|
||||
<!-- -->
|
||||
|
||||
<!-- Send Flow -->
|
||||
<string name="send_hint_input_zcash_address">Enter a shielded Zcash address</string>
|
||||
<string name="send_hint_input_zcash_amount">Enter an amount to send</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_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_failed_button_text">Retry</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>
|
||||
<!-- Screen: Backup -->
|
||||
<string name="backup_button_done" tools:ignore="MissingTranslation">@string/translated_button_done</string>
|
||||
<string name="backup_button_primary">@string/custom_translation_verify</string>
|
||||
<string name="backup_format_birthday_height">@string/custom_translation_birthday</string>
|
||||
<string name="backup_instruction_store_words">Store these backup words securely.</string>
|
||||
<string name="backup_slogan">empowering\neveryone\nwith\neconomic\nfreedom</string>
|
||||
<string name="backup_verification_not_implemented">Backup verification coming soon!</string>
|
||||
|
||||
<!-- Address -->
|
||||
<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>
|
||||
<!-- Screen: Feedback -->
|
||||
<string name="feedback_hint_1">My experience was . . .</string>
|
||||
<string name="feedback_hint_2">My balance was . . .</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 -->
|
||||
<string name="profile_shielded_user">Shielded User</string>
|
||||
<string name="profile_send_feedback">Send Feedback</string>
|
||||
<string name="profile_backup_wallet">Backup Wallet</string>
|
||||
<string name="profile_see_application_logs">See Application Logs</string>
|
||||
<string name="profile_ecc_wallet">ECC Wallet</string>
|
||||
<!-- Screen: History -->
|
||||
<string name="history_address_label">Shielded address:</string>
|
||||
<string name="history_balance_available" tools:ignore="MissingTranslation">@string/translated_balance_available</string>
|
||||
<string name="history_balance_updating">Updating</string>
|
||||
<string name="history_empty_text">No history yet.</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_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_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_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_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 -->
|
||||
<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 -->
|
||||
<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="format_date_time">@string/custom_translation_format_date_time_brief</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -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
|
||||
|
||||
-->
|
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 6.2 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 22 KiB |
|
@ -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>
|
|
@ -0,0 +1,3 @@
|
|||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<string name="app_name" tools:ignore="MissingTranslation">ECC (QA)</string>
|
||||
</resources>
|
|
@ -1,11 +1,10 @@
|
|||
package cash.z.ecc.android
|
||||
|
||||
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.sdk.entity.*
|
||||
import com.nhaarman.mockitokotlin2.verify
|
||||
import com.nhaarman.mockitokotlin2.verifyZeroInteractions
|
||||
import com.nhaarman.mockitokotlin2.whenever
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.newSingleThreadContext
|
||||
import kotlinx.coroutines.test.setMain
|
||||
|
@ -54,7 +53,7 @@ class SendViewModelTest {
|
|||
|
||||
@Test
|
||||
fun testUpdateMetrics_creating() {
|
||||
// doNothing().whenever(sendViewModel).report(any())
|
||||
doNothing().whenever(sendViewModel).report(any())
|
||||
|
||||
assertEquals(true, creatingTx.isCreating())
|
||||
sendViewModel.updateMetrics(creatingTx)
|
||||
|
|
|
@ -3,12 +3,16 @@ package cash.z.ecc.android
|
|||
object Deps {
|
||||
// For use in the top-level build.gradle which gives an error when provided
|
||||
// `Deps.Kotlin.version` directly
|
||||
const val kotlinVersion = "1.4.10"
|
||||
const val kotlinVersion = "1.4.10"
|
||||
|
||||
const val compileSdkVersion = 29
|
||||
const val buildToolsVersion = "29.0.2"
|
||||
const val minSdkVersion = 21
|
||||
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"
|
||||
|
||||
const val compileSdkVersion = 29
|
||||
const val buildToolsVersion = "29.0.2"
|
||||
const val minSdkVersion = 21
|
||||
const val targetSdkVersion = 29
|
||||
|
||||
object AndroidX {
|
||||
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_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 UI_KTX = "androidx.navigation:navigation-ui-ktx:$version"
|
||||
}
|
||||
|
@ -80,25 +84,26 @@ object Deps {
|
|||
}
|
||||
object Zcash {
|
||||
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"
|
||||
object Sdk : Version("1.1.0-beta07") {
|
||||
const val KOTLIN_BIP39 = "cash.z.ecc.android:kotlin-bip39:1.0.1"
|
||||
object Sdk : Version("1.1.0-beta08") {
|
||||
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 {
|
||||
const val LOTTIE = "com.airbnb.android:lottie:3.1.0"
|
||||
const val CHIPS = "com.github.gmale:chips-input-layout:2.3.1"
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
object Test {
|
||||
const val JUNIT = "junit:junit:4.12"
|
||||
const val MOKITO = "junit:junit:4.12"
|
||||
const val COROUTINES_TEST = "junit:junit:4.12"
|
||||
const val MOKITO = "org.mockito:mockito-android:3.5.10"
|
||||
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 {
|
||||
const val JUNIT = "androidx.test.ext:junit:1.1.1"
|
||||
const val ESPRESSO = "androidx.test.espresso:espresso-core:3.2.0"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package cash.z.ecc.android.lockbox
|
||||
|
||||
import android.content.Context
|
||||
import android.provider.Settings
|
||||
import cash.z.android.plugin.LockBoxPlugin
|
||||
import de.adorsys.android.securestoragelibrary.SecurePreferences
|
||||
import java.nio.ByteBuffer
|
||||
|
@ -11,60 +12,72 @@ import javax.inject.Inject
|
|||
|
||||
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) {
|
||||
SecurePreferences.setValue(appContext, key, value)
|
||||
setChunkedString(key, value.toString())
|
||||
}
|
||||
|
||||
override fun getBoolean(key: String): Boolean {
|
||||
return SecurePreferences.getBooleanValue(appContext, key, false)
|
||||
return getChunkedString(key)?.toBoolean() ?: false
|
||||
}
|
||||
|
||||
override fun setBytes(key: String, value: ByteArray) {
|
||||
// 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
|
||||
setValue(key, value.toHex())
|
||||
setChunkedString(key, value.toHex())
|
||||
}
|
||||
|
||||
override fun getBytes(key: String): ByteArray? {
|
||||
return getValue(key)?.fromHex()
|
||||
return getChunkedString(key)?.fromHex()
|
||||
}
|
||||
|
||||
override fun setCharsUtf8(key: String, value: CharArray) {
|
||||
// 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
|
||||
setValue(key, String(value))
|
||||
setChunkedString(key, String(value))
|
||||
}
|
||||
|
||||
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) {
|
||||
when (T::class.java) {
|
||||
Boolean::class.java, Double::class.java, Float::class.java, Integer::class.java, Long::class.java, String::class.java -> setValue(key, value.toString())
|
||||
else -> throw UnsupportedOperationException("Lockbox does not yet support ${T::class.java.simpleName} objects but it can be added")
|
||||
when (T::class) {
|
||||
Boolean::class -> setBoolean(key, value as Boolean)
|
||||
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) {
|
||||
Boolean::class.java -> (getCharsUtf8(key)?.let { String(it).toIntOrNull() }) as T
|
||||
Double::class.java -> (getCharsUtf8(key)?.let { String(it).toDoubleOrNull() }) as T
|
||||
Float::class.java -> (getCharsUtf8(key)?.let { String(it).toFloatOrNull() }) as T
|
||||
Integer::class.java -> (getCharsUtf8(key)?.let { String(it).toIntOrNull() }) as T
|
||||
Long::class.java -> (getCharsUtf8(key)?.let { String(it).toLongOrNull() }) as T
|
||||
String::class.java -> (getCharsUtf8(key)?.let { String(it) }) as T
|
||||
else -> throw UnsupportedOperationException("Lockbox does not yet support ${T::class.java.simpleName} objects but it can be added")
|
||||
}
|
||||
inline operator fun <reified T> get(key: String): T? = when (T::class) {
|
||||
Boolean::class -> getBoolean(key)
|
||||
ByteArray::class -> getBytes(key)
|
||||
CharArray::class -> getCharsUtf8(key)
|
||||
Double::class -> getChunkedString(key)?.let { it.toDoubleOrNull() }
|
||||
Float::class -> getChunkedString(key)?.let { it.toFloatOrNull() }
|
||||
Integer::class -> getChunkedString(key)?.let { it.toIntOrNull() }
|
||||
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
|
||||
* String that can be stored.
|
||||
*/
|
||||
fun setValue(key: String, value: String) {
|
||||
fun setChunkedString(key: String, value: String) {
|
||||
if (value.length > maxLength) {
|
||||
SecurePreferences.setValue(appContext, key, value.chunked(maxLength).toSet())
|
||||
SecurePreferences.setValue(appContext, key, value.chunked(maxLength))
|
||||
} else {
|
||||
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.
|
||||
*/
|
||||
private fun getValue(key: String): String? {
|
||||
fun getChunkedString(key: String): String? {
|
||||
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("")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
<resources>
|
||||
<string name="app_name">QRecycler</string>
|
||||
</resources>
|
||||
|
|