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:
parent
65edb2f69a
commit
29c024c563
|
@ -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)
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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" />
|
||||
<!--
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue