Iterated on wallet creation

- Added additional metrics
 - Added support for using a funded wallet for development
 - Added (hidden) logic for importing an existing wallet
This commit is contained in:
Kevin Gorham 2019-12-23 14:12:34 -05:00
parent 65edb2f69a
commit 29c024c563
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
10 changed files with 247 additions and 69 deletions

View File

@ -2,15 +2,25 @@ package cash.z.ecc.android.feedback
import cash.z.ecc.android.ZcashWalletApp
enum class NonUserAction(override val key: String, val description: String) : Feedback.Action {
FEEDBACK_STARTED("action.feedback.start", "feedback started"),
FEEDBACK_STOPPED("action.feedback.stop", "feedback stopped");
object Report {
enum class NonUserAction(override val key: String, val description: String) : Feedback.Action {
FEEDBACK_STARTED("action.feedback.start", "feedback started"),
FEEDBACK_STOPPED("action.feedback.stop", "feedback stopped"),
SYNC_START("action.feedback.synchronizer.start", "sync started");
override fun toString(): String = description
}
override fun toString(): String = description
}
enum class MetricType(override val key: String, val description: String) : Feedback.Action {
SEED_CREATION("metric.seed.creation", "seed created")
enum class MetricType(override val key: String, val description: String) : Feedback.Action {
ENTROPY_CREATED("metric.entropy.created", "entropy created"),
SEED_CREATED("metric.seed.created", "seed created"),
SEED_IMPORTED("metric.seed.imported", "seed imported"),
SEED_PHRASE_CREATED("metric.seedphrase.created", "seed phrase created"),
SEED_PHRASE_LOADED("metric.seedphrase.loaded", "seed phrase loaded"),
WALLET_CREATED("metric.wallet.created", "wallet created"),
WALLET_IMPORTED("metric.wallet.imported", "wallet imported"),
ACCOUNT_CREATED("metric.account.created", "account created")
}
}
class LaunchMetric private constructor(private val metric: Feedback.TimeMetric) :
@ -27,5 +37,5 @@ class LaunchMetric private constructor(private val metric: Feedback.TimeMetric)
override fun toString(): String = metric.toString()
}
fun <T> Feedback.measure(type: MetricType, block: () -> T) =
inline fun <T> Feedback.measure(type: Report.MetricType, block: () -> T): T =
this.measure(type.key, type.description, block)

View File

@ -13,7 +13,9 @@ import android.view.ViewGroup
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.activity.viewModels
import androidx.core.content.getSystemService
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
import androidx.navigation.findNavController
@ -21,6 +23,12 @@ import cash.z.ecc.android.R
import cash.z.ecc.android.ZcashWalletApp
import cash.z.ecc.android.di.annotation.ActivityScope
import cash.z.ecc.android.feedback.*
import cash.z.ecc.android.feedback.Report.NonUserAction.FEEDBACK_STOPPED
import cash.z.ecc.android.feedback.Report.NonUserAction.SYNC_START
import cash.z.ecc.android.ui.send.SendViewModel
import cash.z.wallet.sdk.Initializer
import cash.z.wallet.sdk.Synchronizer
import cash.z.wallet.sdk.ext.twig
import com.google.android.material.snackbar.Snackbar
import dagger.Module
import dagger.Provides
@ -33,6 +41,8 @@ import javax.inject.Inject
class MainActivity : DaggerAppCompatActivity() {
private var syncInit: (() -> Unit)? = null
@Inject
lateinit var feedback: Feedback
@ -78,7 +88,7 @@ class MainActivity : DaggerAppCompatActivity() {
override fun onDestroy() {
lifecycleScope.launch {
feedback.report(NonUserAction.FEEDBACK_STOPPED)
feedback.report(FEEDBACK_STOPPED)
feedback.stop()
}
super.onDestroy()
@ -106,6 +116,29 @@ class MainActivity : DaggerAppCompatActivity() {
}
}
fun initSync() {
twig("Initializing synchronizer")
if (!::synchronizer.isInitialized) {
twig("Synchronizer didn't exist yet (this means we're opening an existing wallet). Creating it now.")
val initializer = Initializer(ZcashWalletApp.instance, "lightd-main.zecwallet.co", 443).also { it.open() }
synchronizer = Synchronizer(ZcashWalletApp.instance, initializer)
}
feedback.report(SYNC_START)
synchronizer.start(lifecycleScope)
if (syncInit != null) {
syncInit!!()
syncInit = null
}
}
fun initializeAccount(seed: ByteArray, birthday: Initializer.WalletBirthday? = null) {
twig("Initializing accounts")
feedback.measure(Report.MetricType.ACCOUNT_CREATED) {
synchronizer =
Synchronizer(ZcashWalletApp.instance, "lightd-main.zecwallet.co", 443, seed, birthday)
}
}
fun playSound(fileName: String) {
mediaPlayer.apply {
if (isPlaying) stop()
@ -174,6 +207,15 @@ class MainActivity : DaggerAppCompatActivity() {
if (!it.isShownOrQueued) it.show()
}
}
// TODO: refactor initialization and remove the need for this
fun onSyncInit(initBlock: () -> Unit) {
if (::synchronizer.isInitialized) {
initBlock()
} else {
syncInit = initBlock
}
}
}
@Module

View File

@ -5,15 +5,23 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.NonNull
import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding
import cash.z.ecc.android.ui.MainActivity
import dagger.android.support.DaggerFragment
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlin.coroutines.coroutineContext
abstract class BaseFragment<T : ViewBinding> : DaggerFragment() {
val mainActivity: MainActivity? get() = activity as MainActivity?
lateinit var binding: T
lateinit var resumedScope: CoroutineScope
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -23,6 +31,18 @@ abstract class BaseFragment<T : ViewBinding> : DaggerFragment() {
return binding.root
}
override fun onResume() {
super.onResume()
resumedScope = lifecycleScope.coroutineContext.let {
CoroutineScope(it + SupervisorJob(it[Job]))
}
}
override fun onPause() {
super.onPause()
resumedScope.cancel()
}
// inflate is static in the ViewBinding class so we can't handle this ourselves
// each fragment must call FragmentMyLayoutBinding.inflate(inflater)
abstract fun inflate(@NonNull inflater: LayoutInflater): T

View File

@ -12,19 +12,17 @@ import cash.z.ecc.android.R
import cash.z.ecc.android.ZcashWalletApp
import cash.z.ecc.android.databinding.FragmentLandingBinding
import cash.z.ecc.android.di.annotation.FragmentScope
import cash.z.ecc.android.feedback.MetricType.SEED_CREATION
import cash.z.ecc.android.feedback.measure
import cash.z.ecc.android.isEmulator
import cash.z.ecc.android.lockbox.LockBox
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_WITHOUT_BACKUP
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.SEED_WITH_BACKUP
import cash.z.ecc.kotlin.mnemonic.Mnemonics
import cash.z.wallet.sdk.Initializer
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.Module
import dagger.android.ContributesAndroidInjector
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import javax.inject.Inject
class LandingFragment : BaseFragment<FragmentLandingBinding>() {
@ -46,6 +44,24 @@ class LandingFragment : BaseFragment<FragmentLandingBinding>() {
"backup" -> onBackupWallet()
}
}
binding.buttonNegative.setOnLongClickListener {
if (binding.buttonNegative.text.toString().toLowerCase() == "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?")
.setCancelable(true)
.setPositiveButton("Import") { dialog, _ ->
dialog.dismiss()
onUseDevWallet()
}
.setNegativeButton("Cancel") { dialog, _ ->
dialog.dismiss()
}
.show()
true
}
false
}
binding.buttonNegative.setOnClickListener {
when (binding.buttonNegative.text.toString().toLowerCase()) {
"restore" -> onRestoreWallet()
@ -84,23 +100,50 @@ class LandingFragment : BaseFragment<FragmentLandingBinding>() {
}
private fun onRestoreWallet() {
if (ZcashWalletApp.instance.isEmulator()) {
onEnterWallet()
} else {
Toast.makeText(activity, "Coming soon!", Toast.LENGTH_SHORT).show()
Toast.makeText(activity, "Coming soon!", Toast.LENGTH_SHORT).show()
}
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
mainActivity?.apply {
lifecycleScope.launch {
initializeAccount(
walletSetup.importWallet(feedback, seedPhrase.toCharArray()),
Initializer.loadBirthdayFromAssets(ZcashWalletApp.instance, birthday)
)
initSync()
}
binding.buttonPositive.isEnabled = true
binding.textMessage.text = "Wallet imported! Congratulations!"
binding.buttonNegative.text = "Skip"
binding.buttonPositive.text = "Backup"
playSound("sound_receive_small.mp3")
vibrateSuccess()
}
}
// TODO: move this to the ViewModel but doing so requires fixing dagger so do that as a separate PR
private fun onNewWallet() {
mainActivity?.feedback?.measure(SEED_CREATION) {
walletSetup.createSeed()
}
lifecycleScope.launch {
val ogText = binding.buttonPositive.text
binding.buttonPositive.text = "creating"
binding.buttonPositive.isEnabled = false
binding.textMessage.text = "Wallet created! Congratulations!"
binding.buttonNegative.text = "Skip"
binding.buttonPositive.text = "Backup"
mainActivity?.playSound("sound_receive_small.mp3")
mainActivity?.vibrateSuccess()
mainActivity?.apply {
initializeAccount(walletSetup.createWallet(feedback))
initSync()
}
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()
}
}
private fun onBackupWallet() {

View File

@ -1,14 +1,16 @@
package cash.z.ecc.android.ui.setup
import androidx.lifecycle.ViewModel
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.ui.setup.WalletSetupViewModel.LockBoxKey.HAS_BACKUP
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.LockBoxKey.HAS_SEED
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.LockBoxKey.SEED
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.*
import cash.z.ecc.kotlin.mnemonic.Mnemonics
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext
import javax.inject.Inject
class WalletSetupViewModel @Inject constructor(val mnemonics: Mnemonics, val lockBox: LockBox) :
@ -20,27 +22,80 @@ class WalletSetupViewModel @Inject constructor(val mnemonics: Mnemonics, val loc
fun checkSeed(): Flow<WalletSetupState> = flow {
when {
lockBox.getBoolean(HAS_BACKUP) -> emit(SEED_WITH_BACKUP)
lockBox.getBoolean(HAS_SEED) -> emit(SEED_WITHOUT_BACKUP)
lockBox.getBoolean(LockBoxKey.HAS_BACKUP) -> emit(SEED_WITH_BACKUP)
lockBox.getBoolean(LockBoxKey.HAS_SEED) -> emit(SEED_WITHOUT_BACKUP)
else -> emit(NO_SEED)
}
}
fun createSeed() {
check(!lockBox.getBoolean(HAS_SEED)) {
/**
* Take all the steps necessary to create a new wallet and measure how long it takes.
*
* @param feedback the object used for measurement.
*/
suspend fun createWallet(feedback: Feedback): 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!"
}
mnemonics.apply {
lockBox.setBytes(SEED, nextSeed())
lockBox.setBoolean(HAS_SEED, true)
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 ->
lockBox.setCharsUtf8(LockBoxKey.SEED_PHRASE, seedPhrase)
lockBox.setBoolean(LockBoxKey.HAS_SEED_PHRASE, true)
lockBox.setBytes(LockBoxKey.SEED, bip39Seed)
lockBox.setBoolean(LockBoxKey.HAS_SEED, true)
bip39Seed
}
}
}
}
}
}
/**
* Take all the steps necessary to import a wallet and measure how long it takes.
*
* @param feedback the object used for measurement.
*/
suspend fun importWallet(
feedback: Feedback,
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 ->
lockBox.setCharsUtf8(LockBoxKey.SEED_PHRASE, seedPhrase)
lockBox.setBoolean(LockBoxKey.HAS_SEED_PHRASE, true)
lockBox.setBytes(LockBoxKey.SEED, bip39Seed)
lockBox.setBoolean(LockBoxKey.HAS_SEED, true)
bip39Seed
}
}
}
}
object LockBoxKey {
const val SEED = "cash.z.ecc.android.SEED1"
const val HAS_SEED = "cash.z.ecc.android.HAS_SEED1"
const val HAS_BACKUP = "cash.z.ecc.android.HAS_BACKUP1"
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"
}
}

View File

@ -4,6 +4,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:background="@drawable/background_home">
<androidx.constraintlayout.widget.Guideline
@ -39,7 +40,7 @@
style="@style/Zcash.TextAppearance.AddressPart"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="drum"
tools:text="drum"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_4"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
@ -51,7 +52,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="word"
tools:text="word"
app:layout_constraintStart_toStartOf="@id/text_address_part_1"
app:layout_constraintTop_toBottomOf="@id/text_address_part_1"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_7" />
@ -61,7 +62,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="word"
tools:text="word"
app:layout_constraintStart_toStartOf="@id/text_address_part_1"
app:layout_constraintTop_toBottomOf="@id/text_address_part_4"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_10" />
@ -71,7 +72,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="word"
tools:text="word"
app:layout_constraintStart_toStartOf="@id/text_address_part_1"
app:layout_constraintTop_toBottomOf="@id/text_address_part_7"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_13" />
@ -81,7 +82,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="word"
tools:text="word"
app:layout_constraintStart_toStartOf="@id/text_address_part_1"
app:layout_constraintTop_toBottomOf="@id/text_address_part_10"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_16" />
@ -91,7 +92,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="word"
tools:text="word"
app:layout_constraintStart_toStartOf="@id/text_address_part_1"
app:layout_constraintTop_toBottomOf="@id/text_address_part_13"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_19" />
@ -101,7 +102,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="word"
tools:text="word"
app:layout_constraintStart_toStartOf="@id/text_address_part_1"
app:layout_constraintTop_toBottomOf="@id/text_address_part_16"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_22" />
@ -111,7 +112,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="word"
tools:text="word"
app:layout_constraintStart_toStartOf="@id/text_address_part_1"
app:layout_constraintTop_toBottomOf="@id/text_address_part_19" />
@ -126,7 +127,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:text="fitness"
tools:text="fitness"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_5"
app:layout_constraintStart_toEndOf="@id/barrier_left_address_column_1"
app:layout_constraintTop_toTopOf="@id/text_address_part_1" />
@ -137,7 +138,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
style="@style/Zcash.TextAppearance.AddressPart"
android:text="word"
tools:text="word"
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_2" />
@ -148,7 +149,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
style="@style/Zcash.TextAppearance.AddressPart"
android:text="word"
tools:text="word"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_11"
app:layout_constraintStart_toStartOf="@id/text_address_part_2"
app:layout_constraintTop_toBottomOf="@id/text_address_part_5" />
@ -159,7 +160,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
style="@style/Zcash.TextAppearance.AddressPart"
android:text="word"
tools:text="word"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_14"
app:layout_constraintStart_toStartOf="@id/text_address_part_2"
app:layout_constraintTop_toBottomOf="@id/text_address_part_8" />
@ -170,7 +171,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
style="@style/Zcash.TextAppearance.AddressPart"
android:text="word"
tools:text="word"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_17"
app:layout_constraintStart_toStartOf="@id/text_address_part_2"
app:layout_constraintTop_toBottomOf="@id/text_address_part_11" />
@ -181,7 +182,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
style="@style/Zcash.TextAppearance.AddressPart"
android:text="word"
tools:text="word"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_20"
app:layout_constraintStart_toStartOf="@id/text_address_part_2"
app:layout_constraintTop_toBottomOf="@id/text_address_part_14" />
@ -192,7 +193,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
style="@style/Zcash.TextAppearance.AddressPart"
android:text="word"
tools:text="word"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_23"
app:layout_constraintStart_toStartOf="@id/text_address_part_2"
app:layout_constraintTop_toBottomOf="@id/text_address_part_17" />
@ -203,7 +204,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
style="@style/Zcash.TextAppearance.AddressPart"
android:text="word"
tools:text="word"
app:layout_constraintStart_toStartOf="@id/text_address_part_2"
app:layout_constraintTop_toBottomOf="@id/text_address_part_20" />
@ -218,7 +219,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:text="goals"
tools:text="goals"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_6"
app:layout_constraintStart_toEndOf="@id/barrier_left_address_column_2"
app:layout_constraintTop_toTopOf="@id/text_address_part_1" />
@ -229,7 +230,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
style="@style/Zcash.TextAppearance.AddressPart"
android:text="word"
tools:text="word"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_9"
app:layout_constraintStart_toStartOf="@id/text_address_part_3"
app:layout_constraintTop_toBottomOf="@id/text_address_part_3" />
@ -240,7 +241,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
style="@style/Zcash.TextAppearance.AddressPart"
android:text="word"
tools:text="word"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_12"
app:layout_constraintStart_toStartOf="@id/text_address_part_3"
app:layout_constraintTop_toBottomOf="@id/text_address_part_6" />
@ -251,7 +252,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
style="@style/Zcash.TextAppearance.AddressPart"
android:text="word"
tools:text="word"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_15"
app:layout_constraintStart_toStartOf="@id/text_address_part_3"
app:layout_constraintTop_toBottomOf="@id/text_address_part_9" />
@ -262,7 +263,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
style="@style/Zcash.TextAppearance.AddressPart"
android:text="word"
tools:text="word"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_18"
app:layout_constraintStart_toStartOf="@id/text_address_part_3"
app:layout_constraintTop_toBottomOf="@id/text_address_part_12" />
@ -273,7 +274,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
style="@style/Zcash.TextAppearance.AddressPart"
android:text="word"
tools:text="word"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_21"
app:layout_constraintStart_toStartOf="@id/text_address_part_3"
app:layout_constraintTop_toBottomOf="@id/text_address_part_15" />
@ -284,7 +285,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
style="@style/Zcash.TextAppearance.AddressPart"
android:text="word"
tools:text="word"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_24"
app:layout_constraintStart_toStartOf="@id/text_address_part_3"
app:layout_constraintTop_toBottomOf="@id/text_address_part_18" />
@ -295,7 +296,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
style="@style/Zcash.TextAppearance.AddressPart"
android:text="word"
tools:text="word"
app:layout_constraintStart_toStartOf="@id/text_address_part_3"
app:layout_constraintTop_toBottomOf="@id/text_address_part_21" />
<!--

View File

@ -49,7 +49,7 @@
android:gravity="center"
android:paddingStart="32dp"
android:paddingEnd="32dp"
android:text="Welcome to the Birch Wallet!"
android:text="Welcome to the zECC Wallet!"
android:textColor="@color/zcashWhite"
app:layout_constraintBottom_toTopOf="@id/guideline_buttons"
app:layout_constraintEnd_toEndOf="parent"

View File

@ -12,7 +12,9 @@
tools:layout="@layout/fragment_landing" >
<action
android:id="@+id/action_nav_landing_to_nav_backup"
app:destination="@id/nav_backup" />
app:destination="@id/nav_backup"
app:popUpTo="@id/nav_landing"
app:popUpToInclusive="true"/>
</fragment>
<fragment

View File

@ -92,12 +92,13 @@ class Feedback(capacity: Int = 256) {
* will run concurrently--meaning a "happens before" relationship between the measurer and the
* measured cannot be established and thereby the concurrent action cannot be timed.
*/
inline fun <T> measure(key: String = "measurement.generic", description: Any = "measurement", block: () -> T) {
inline fun <T> measure(key: String = "measurement.generic", description: Any = "measurement", block: () -> T): T {
ensureScope()
val metric = TimeMetric(key, description.toString()).markTime()
block()
val result = block()
metric.markTime()
report(metric)
return result
}
/**

View File

@ -19,6 +19,8 @@ class LockBox @Inject constructor(private val appContext: Context) : LockBoxProv
}
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
SecurePreferences.setValue(appContext, key, value.toHex())
}
@ -27,11 +29,13 @@ class LockBox @Inject constructor(private val appContext: Context) : LockBoxProv
}
override fun setCharsUtf8(key: String, value: CharArray) {
setBytes(key, value.toBytes())
// 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
SecurePreferences.setValue(appContext, key, String(value))
}
override fun getCharsUtf8(key: String): CharArray? {
return getBytes(key)?.fromBytes()
return SecurePreferences.getStringValue(appContext, key, null)?.toCharArray()
}