Demo app improvements.

including: mainnet support, BIP39 support and using seed words instead of string-based seeds, several convenience functions like copyToClipboard, simplified the developer experience for using the FAB, corrected errors in port numbers, streamlined several demos, trim user input, better messaging using new processorInfo flow.
This commit is contained in:
Kevin Gorham 2020-02-26 02:43:27 -05:00
parent 250217d0cd
commit 508e6d3da9
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
19 changed files with 328 additions and 82 deletions

View File

@ -15,7 +15,21 @@ android {
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
missingDimensionStrategy "network", "zcashtestnet"
}
flavorDimensions 'network'
productFlavors {
// would rather name them "testnet" and "mainnet" but product flavor names cannot start with the word "test"
zcashtestnet {
dimension 'network'
applicationId 'cash.z.wallet.sdk.demoapp.testnet'
matchingFallbacks = ['zcashtestnet', 'debug']
}
zcashmainnet {
dimension 'network'
applicationId 'cash.z.wallet.sdk.demoapp.mainnet'
matchingFallbacks = ['zcashmainnet', 'release']
}
}
buildTypes {
release {
@ -36,7 +50,12 @@ dependencies {
// SDK
implementation project(path: ':sdk')
// implementation "cash.z.android.wallet:zcash-android-core:$sdk_version@aar"
// implementation "cash.z.android.wallet:zcash-kotlin-bip39:$sdk_version@aar"
// sample mnemonic plugin
implementation 'com.github.zcash:zcash-android-wallet-plugins:1.0.0'
implementation 'com.madgag.spongycastle:core:1.58.0.0'
implementation 'io.github.novacrypto:BIP39:2019.01.27'
implementation 'io.github.novacrypto:securestring:2019.01.27'
// SDK: grpc
implementation "io.grpc:grpc-okhttp:1.21.0"

View File

@ -1,7 +1,6 @@
package cash.z.wallet.sdk.demoapp
import android.app.Application
import cash.z.wallet.sdk.demoapp.util.DemoConfig
class App : Application() {

View File

@ -1,14 +1,19 @@
package cash.z.wallet.sdk.demoapp
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding
import cash.z.wallet.sdk.ext.TroubleshootingTwig
import cash.z.wallet.sdk.ext.Twig
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.withContext
@ -32,6 +37,7 @@ abstract class BaseDemoFragment<T : ViewBinding> : Fragment() {
override fun onResume() {
super.onResume()
registerActionButtonListener()
// just a quick way of enforcing the following for each demo:
// - wait until the fragment is created, then run `initInBackground` on a background thread
// - wait until init is finished
@ -59,9 +65,58 @@ abstract class BaseDemoFragment<T : ViewBinding> : Fragment() {
override fun onPause() {
super.onPause()
unregisterActionButtonListener()
onClear()
}
private fun registerActionButtonListener() {
(activity as? MainActivity)?.fabListener = this
}
private fun unregisterActionButtonListener() {
(activity as? MainActivity)?.apply {
if (fabListener === this@BaseDemoFragment) fabListener = null
}
}
/**
* Callback to run whenever the fragment is paused. The intention is to clear out each demo
* cleanly so that they are always a repeatable experience.
*/
open fun onClear() {}
/**
* Callback that gets invoked on the visible fragment whenever the floating action button is
* tapped. This provides a convenient placeholder for the developer to extend the
* behavior for a demo, for instance by copying the address to the clipboard, whenever the FAB
* is tapped on the address screen.
*/
open fun onActionButtonClicked() {
// Show a message so that it's easy for developers to find how to replace this behavior for
// each fragment. Simply override this [onActionButtonClicked] callback to add behavior to a
// demo. In other words, this function probably doesn't need to change because desired
// behavior should go in the child fragment, which overrides this.
Snackbar.make(view!!, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action") { /* auto-close */ }.show()
}
/**
* Convenience function to the given text to the clipboard.
*/
open fun copyToClipboard(text: String) {
(activity?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)?.let { cm ->
cm.setPrimaryClip(ClipData.newPlainText("DemoAppClip", text))
}
toast("Copied to clipboard!")
}
/**
* Convenience function to show a toast in the main activity.
*/
fun toast(message: String) {
Toast.makeText(activity, message, Toast.LENGTH_SHORT).show()
}
/**
* Inflate the ViewBinding. Unfortunately, the `inflate` function is not part of the ViewBinding
* interface so the base class cannot take care of this behavior without some help.
@ -69,5 +124,4 @@ abstract class BaseDemoFragment<T : ViewBinding> : Fragment() {
abstract fun inflateBinding(layoutInflater: LayoutInflater): T
abstract fun resetInBackground()
abstract fun onResetComplete()
abstract fun onClear()
}

View File

@ -2,6 +2,7 @@ package cash.z.wallet.sdk.demoapp
import android.os.Bundle
import android.view.Menu
import android.view.View
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.navigation.NavigationView
@ -13,11 +14,12 @@ import androidx.navigation.ui.setupWithNavController
import androidx.drawerlayout.widget.DrawerLayout
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import cash.z.wallet.sdk.demoapp.util.DemoConfig
import androidx.fragment.app.Fragment
import androidx.viewbinding.ViewBinding
class MainActivity : AppCompatActivity() {
private lateinit var appBarConfiguration: AppBarConfiguration
var fabListener: BaseDemoFragment<out ViewBinding>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -27,8 +29,7 @@ class MainActivity : AppCompatActivity() {
val fab: FloatingActionButton = findViewById(R.id.fab)
fab.setOnClickListener { view ->
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show()
onFabClicked(view)
}
val drawerLayout: DrawerLayout = findViewById(R.id.drawer_layout)
val navView: NavigationView = findViewById(R.id.nav_view)
@ -46,6 +47,10 @@ class MainActivity : AppCompatActivity() {
navView.setupWithNavController(navController)
}
private fun onFabClicked(view: View) {
fabListener?.onActionButtonClicked()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.main, menu)

View File

@ -8,27 +8,24 @@ import cash.z.wallet.sdk.demoapp.databinding.FragmentGetAddressBinding
class GetAddressFragment : BaseDemoFragment<FragmentGetAddressBinding>() {
private val config = App.instance.defaultConfig
private var seed: ByteArray = config.seed
private val initializer: Initializer = Initializer(App.instance, host = config.host, port = config.port)
private val birthday = config.newWalletBirthday()
private var seed: ByteArray = App.instance.defaultConfig.seed
private val initializer: Initializer = Initializer(App.instance)
private lateinit var address: String
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentGetAddressBinding
= FragmentGetAddressBinding.inflate(layoutInflater)
override fun resetInBackground() {
/**
* Create and initialize the wallet. Initialization will return the private keys but for the
* purposes of this demo we don't need them.
*/
initializer.new(seed, birthday)
address = initializer.deriveAddress(seed)
}
override fun onResetComplete() {
binding.textInfo.text = initializer.rustBackend.getAddress()
binding.textInfo.text = address
}
override fun onClear() {
initializer.clear()
override fun onActionButtonClicked() {
copyToClipboard(address)
}
}

View File

@ -10,6 +10,7 @@ import cash.z.wallet.sdk.service.LightWalletService
class GetBlockFragment : BaseDemoFragment<FragmentGetBlockBinding>() {
private val host = App.instance.defaultConfig.host
private val port = App.instance.defaultConfig.port
private lateinit var lightwalletService: LightWalletService
@ -17,7 +18,7 @@ class GetBlockFragment : BaseDemoFragment<FragmentGetBlockBinding>() {
FragmentGetBlockBinding.inflate(layoutInflater)
override fun resetInBackground() {
lightwalletService = LightWalletGrpcService(App.instance, host)
lightwalletService = LightWalletGrpcService(App.instance, host, port)
}
override fun onResetComplete() {

View File

@ -11,6 +11,7 @@ import cash.z.wallet.sdk.service.LightWalletService
class GetBlockRangeFragment : BaseDemoFragment<FragmentGetBlockRangeBinding>() {
private val host = App.instance.defaultConfig.host
private val port = App.instance.defaultConfig.port
private lateinit var lightwalletService: LightWalletService
@ -18,7 +19,7 @@ class GetBlockRangeFragment : BaseDemoFragment<FragmentGetBlockRangeBinding>() {
FragmentGetBlockRangeBinding.inflate(layoutInflater)
override fun resetInBackground() {
lightwalletService = LightWalletGrpcService(App.instance, host)
lightwalletService = LightWalletGrpcService(App.instance, host, port)
}
override fun onResetComplete() {

View File

@ -10,6 +10,7 @@ import cash.z.wallet.sdk.service.LightWalletService
class GetLatestHeightFragment : BaseDemoFragment<FragmentGetLatestHeightBinding>() {
private val host = App.instance.defaultConfig.host
private val port = App.instance.defaultConfig.port
private lateinit var lightwalletService: LightWalletService
@ -17,7 +18,7 @@ class GetLatestHeightFragment : BaseDemoFragment<FragmentGetLatestHeightBinding>
FragmentGetLatestHeightBinding.inflate(layoutInflater)
override fun resetInBackground() {
lightwalletService = LightWalletGrpcService(App.instance, host)
lightwalletService = LightWalletGrpcService(App.instance, host, port)
}
override fun onResetComplete() {
@ -27,4 +28,9 @@ class GetLatestHeightFragment : BaseDemoFragment<FragmentGetLatestHeightBinding>
override fun onClear() {
lightwalletService.shutdown()
}
override fun onActionButtonClicked() {
toast("Refreshed!")
onResetComplete()
}
}

View File

@ -26,7 +26,7 @@ class GetPrivateKeyFragment : BaseDemoFragment<FragmentGetPrivateKeyBinding>() {
spendingKeys = initializer.new(seed, birthday)
/*
* Viewing keys can be derived from a seed or from spending keys.
* Alternatively, viewing keys can also be derived directly from a seed or spending keys.
*/
viewingKeys = initializer.deriveViewingKeys(seed)

View File

@ -7,6 +7,7 @@ import androidx.paging.PagedList
import androidx.recyclerview.widget.LinearLayoutManager
import cash.z.wallet.sdk.Initializer
import cash.z.wallet.sdk.Synchronizer
import cash.z.wallet.sdk.block.CompactBlockProcessor
import cash.z.wallet.sdk.demoapp.App
import cash.z.wallet.sdk.demoapp.BaseDemoFragment
import cash.z.wallet.sdk.demoapp.databinding.FragmentListTransactionsBinding
@ -23,9 +24,13 @@ import kotlinx.coroutines.launch
class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBinding>() {
private val config = App.instance.defaultConfig
private val initializer = Initializer(App.instance, host = config.host, port = config.port)
private val birthday = config.newWalletBirthday()
private val birthday = config.loadBirthday()
private lateinit var synchronizer: Synchronizer
private lateinit var adapter: TransactionAdapter<ConfirmedTransaction>
private lateinit var address: String
private var status: Synchronizer.Status? = null
private val isSynced get() = status == Synchronizer.Status.SYNCED
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentListTransactionsBinding =
FragmentListTransactionsBinding.inflate(layoutInflater)
@ -51,6 +56,7 @@ class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBindin
LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false)
adapter = TransactionAdapter()
lifecycleScope.launch {
address = synchronizer.getAddress()
synchronizer.receivedTransactions.collect { onTransactionsUpdated(it) }
}
binding.recyclerTransactions.adapter = adapter
@ -64,20 +70,22 @@ class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBindin
private fun monitorStatus() {
synchronizer.status.collectWith(lifecycleScope, ::onStatus)
synchronizer.processorInfo.collectWith(lifecycleScope, ::onProcessorInfoUpdated)
synchronizer.progress.collectWith(lifecycleScope, ::onProgress)
}
private fun onProcessorInfoUpdated(info: CompactBlockProcessor.ProcessorInfo) {
if (info.isScanning) binding.textInfo.text = "Scanning blocks...${info.scanProgress}%"
}
private fun onProgress(i: Int) {
val message = when (i) {
100 -> "Scanning blocks..."
else -> "Downloading blocks...$i%"
}
binding.textInfo.text = message
if (i < 100) binding.textInfo.text = "Downloading blocks...$i%"
}
private fun onStatus(status: Synchronizer.Status) {
this.status = status
binding.textStatus.text = "Status: $status"
if (status == Synchronizer.Status.SYNCED) onSyncComplete()
if (isSynced) onSyncComplete()
}
private fun onSyncComplete() {
@ -87,5 +95,24 @@ class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBindin
private fun onTransactionsUpdated(transactions: PagedList<ConfirmedTransaction>) {
twig("got a new paged list of transactions")
adapter.submitList(transactions)
// show message when there are no transactions
if (isSynced) {
binding.textInfo.apply {
if (transactions.isEmpty()) {
visibility = View.VISIBLE
text =
"No transactions found. Try to either change the seed words in the" +
" DemoConfig.kt file or send funds to this address (tap the FAB to copy it):\n\n $address"
} else {
visibility = View.INVISIBLE
text = ""
}
}
}
}
override fun onActionButtonClicked() {
if (::address.isInitialized) copyToClipboard(address)
}
}

View File

@ -18,7 +18,7 @@ import cash.z.wallet.sdk.ext.*
class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
private val config = App.instance.defaultConfig
private val initializer = Initializer(App.instance, host = config.host, port = config.port)
private val birthday = config.newWalletBirthday()
private val birthday = config.loadBirthday()
private lateinit var synchronizer: Synchronizer
private lateinit var keyManager: SampleStorageBridge
@ -31,7 +31,7 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
// Observable properties (done without livedata or flows for simplicity)
//
private var availableBalance = -1L
private var balance = CompactBlockProcessor.WalletBalance()
set(value) {
field = value
onUpdateSendButton()
@ -98,40 +98,47 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
private fun monitorChanges() {
synchronizer.status.collectWith(lifecycleScope, ::onStatus)
synchronizer.progress.collectWith(lifecycleScope, ::onProgress)
synchronizer.processorInfo.collectWith(lifecycleScope, ::onProcessorInfoUpdated)
synchronizer.balances.collectWith(lifecycleScope, ::onBalance)
}
private fun onStatus(status: Synchronizer.Status) {
binding.textStatus.text = "Status: $status"
if (status != Synchronizer.Status.SYNCED) {
isSyncing = true
isSyncing = status != Synchronizer.Status.SYNCED
if (status == Synchronizer.Status.SCANNING) {
binding.textBalance.text = "Calculating balance..."
} else {
isSyncing = false
if (!isSyncing) onBalance(balance)
}
}
private fun onProgress(i: Int) {
val message = when (i) {
100 -> "Scanning blocks..."
else -> "Downloading blocks...$i%"
if (i < 100) {
binding.textStatus.text = "Downloading blocks...$i%"
binding.textBalance.visibility = View.INVISIBLE
} else {
binding.textBalance.visibility = View.VISIBLE
}
binding.textStatus.text = message
binding.textBalance.text = ""
}
private fun onProcessorInfoUpdated(info: CompactBlockProcessor.ProcessorInfo) {
if (info.isScanning) binding.textStatus.text = "Scanning blocks...${info.scanProgress}%"
}
private fun onBalance(balance: CompactBlockProcessor.WalletBalance) {
availableBalance = balance.availableZatoshi
binding.textBalance.text = """
Available balance: ${balance.availableZatoshi.convertZatoshiToZecString()}
Total balance: ${balance.totalZatoshi.convertZatoshiToZecString()}
""".trimIndent()
this.balance = balance
if (!isSyncing) {
binding.textBalance.text = """
Available balance: ${balance.availableZatoshi.convertZatoshiToZecString(12)}
Total balance: ${balance.totalZatoshi.convertZatoshiToZecString(12)}
""".trimIndent()
}
}
private fun onSend(unused: View) {
isSending = true
val amount = amountInput.text.toString().toDouble().convertZecToZatoshi()
val toAddress = addressInput.text.toString()
val toAddress = addressInput.text.toString().trim()
synchronizer.sendToAddress(
keyManager.key,
amount,
@ -169,7 +176,7 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
text = "⌛ syncing"
isEnabled = false
}
availableBalance <= 0 -> isEnabled = false
balance.availableZatoshi <= 0 -> isEnabled = false
else -> {
text = "send"
isEnabled = true

View File

@ -1,19 +0,0 @@
package cash.z.wallet.sdk.demoapp.util
import cash.z.wallet.sdk.Initializer
import cash.z.wallet.sdk.demoapp.App
data class DemoConfig(
val host: String = "34.68.177.238",//"192.168.1.134",//
val port: Int = 9067,
val birthdayHeight: Int = 620_000,//523_240,
val network: ZcashNetwork = ZcashNetwork.TEST_NET,
val seed: ByteArray = "testreferencealice____placeholder".toByteArray(),
val toAddress: String = "ztestsapling1fg82ar8y8whjfd52l0xcq0w3n7nn7cask2scp9rp27njeurr72ychvud57s9tu90fdqgwdt07lg",
val sendAmount: Double = 0.0024
) {
fun newWalletBirthday() = Initializer.DefaultBirthdayStore.loadBirthdayFromAssets(App.instance)
fun loadBirthday() = Initializer.DefaultBirthdayStore.loadBirthdayFromAssets(App.instance, birthdayHeight)
}
enum class ZcashNetwork { MAIN_NET, TEST_NET }

View File

@ -0,0 +1,82 @@
package cash.z.wallet.sdk.demoapp.util
import cash.z.android.plugin.MnemonicPlugin
import io.github.novacrypto.bip39.MnemonicGenerator
import io.github.novacrypto.bip39.SeedCalculator
import io.github.novacrypto.bip39.Words
import io.github.novacrypto.bip39.wordlists.English
import java.security.SecureRandom
class SimpleMnemonics : MnemonicPlugin {
override fun nextEntropy(): ByteArray {
return ByteArray(Words.TWENTY_FOUR.byteLength()).apply {
SecureRandom().nextBytes(this)
}
}
override fun nextMnemonic(): CharArray {
return nextMnemonic(nextEntropy())
}
override fun nextMnemonic(entropy: ByteArray): CharArray {
return StringBuilder().let { builder ->
MnemonicGenerator(English.INSTANCE).createMnemonic(entropy) { c ->
builder.append(c)
}
builder.toString().toCharArray()
}
}
override fun nextMnemonicList(): List<CharArray> {
return nextMnemonicList(nextEntropy())
}
override fun nextMnemonicList(entropy: ByteArray): List<CharArray> {
return WordListBuilder().let { builder ->
MnemonicGenerator(English.INSTANCE).createMnemonic(entropy) { c ->
builder.append(c)
}
builder.wordList
}
}
override fun toSeed(mnemonic: CharArray): ByteArray {
return SeedCalculator().calculateSeed(String(mnemonic), "")
}
override fun toWordList(mnemonic: CharArray): List<CharArray> {
val wordList = mutableListOf<CharArray>()
var cursor = 0
repeat(mnemonic.size) { i ->
val isSpace = mnemonic[i] == ' '
if (isSpace || i == (mnemonic.size - 1)) {
val wordSize = i - cursor + if (isSpace) 0 else 1
wordList.add(CharArray(wordSize).apply {
repeat(wordSize) {
this[it] = mnemonic[cursor + it]
}
})
cursor = i + 1
}
}
return wordList
}
class WordListBuilder {
val wordList = mutableListOf<CharArray>()
fun append(c: CharSequence) {
if (c[0] != English.INSTANCE.space) addWord(c)
}
private fun addWord(c: CharSequence) {
c.length.let { size ->
val word = CharArray(size)
repeat(size) {
word[it] = c[it]
}
wordList.add(word)
}
}
}
}

View File

@ -2,9 +2,16 @@
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
android:layout_height="match_parent">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_content_end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.88" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/text_layout_start_height"
@ -15,7 +22,7 @@
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.2"/>
app:layout_constraintVertical_bias="0.2" />
<com.google.android.material.textfield.TextInputLayout
@ -39,27 +46,27 @@
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/text_info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="loading blocks..."
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_status"/>
app:layout_constraintTop_toBottomOf="@id/text_status" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_transactions"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginTop="32dp"
app:layout_constraintTop_toBottomOf="@id/text_info"
android:layout_height="0dp"
android:layout_margin="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_status"
app:layout_constraintBottom_toTopOf="@id/guideline_content_end"
tools:itemCount="15"
tools:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_transaction"

View File

@ -2,7 +2,6 @@
<string name="app_name">Demo App</string>
<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>
<string name="nav_header_title">Android SDK Demo</string>
<string name="nav_header_subtitle">v1.0.0-alpha02</string>
<string name="nav_header_desc">Navigation header</string>
<string name="action_settings">Settings</string>

View File

@ -0,0 +1,22 @@
package cash.z.wallet.sdk.demoapp
import cash.z.wallet.sdk.Initializer
import cash.z.wallet.sdk.demoapp.util.SimpleMnemonics
data class DemoConfig(
val host: String = "lightwalletd.z.cash",
val port: Int = 9067,
val birthdayHeight: Int = 735_000,
val sendAmount: Double = 0.0018,
// corresponds to address: zs15tzaulx5weua5c7l47l4pku2pw9fzwvvnsp4y80jdpul0y3nwn5zp7tmkcclqaca3mdjqjkl7hx
val seedWords: String = "wish puppy smile loan doll curve hole maze file ginger hair nose key relax knife witness cannon grab despair throw review deal slush frame",
// corresponds to seed: urban kind wise collect social marble riot primary craft lucky head cause syrup odor artist decorate rhythm phone style benefit portion bus truck top
val toAddress: String = "zs1lcdmue7rewgvzh3jd09sfvwq3sumu6hkhpk53q94kcneuffjkdg9e3tyxrugkmpza5c3c5e6eqh"
) {
val seed: ByteArray get() = SimpleMnemonics().toSeed(seedWords.toCharArray())
fun newWalletBirthday() = Initializer.DefaultBirthdayStore.loadBirthdayFromAssets(App.instance)
fun loadBirthday(height: Int = birthdayHeight) = Initializer.DefaultBirthdayStore.loadBirthdayFromAssets(App.instance, height)
}

View File

@ -0,0 +1,4 @@
<resources>
<string name="app_name">Demo App - Mainnet</string>
<string name="nav_header_title">Android SDK Demo : MAINNET</string>
</resources>

View File

@ -20,12 +20,9 @@ buildscript {
allprojects {
repositories {
// mavenLocal()
// flatDir {
// dirs 'libs'
// }
google()
jcenter()
maven { url 'https://jitpack.io' }
}
}

View File

@ -402,10 +402,48 @@ class CompactBlockProcessor(
val lastDownloadRange: IntRange = 0..-1, // empty range
val lastScanRange: IntRange = 0..-1 // empty range
) {
/**
* Returns false when all values match their defaults.
*/
val hasData get() = networkBlockHeight != -1
|| lastScannedHeight != -1
|| lastDownloadedHeight != -1
|| lastDownloadRange != 0..-1
|| lastScanRange != 0..-1
/**
* Returns true when there are more than zero blocks remaining to download.
*/
val isDownloading: Boolean get() = !lastDownloadRange.isEmpty()
&& lastDownloadedHeight < lastDownloadRange.last
/**
* Returns true when downloading has completed and there are more than zero blocks remaining
* to be scanned.
*/
val isScanning: Boolean get() = !isDownloading
&& !lastScanRange.isEmpty()
&& lastScannedHeight < lastScanRange.last
/**
* The amount of scan progress from 0 to 100.
*/
val scanProgress get() = when {
lastScannedHeight <= -1 -> 0
lastScanRange.isEmpty() -> 100
lastScannedHeight >= lastScanRange.last -> 100
else -> {
// when lastScannedHeight == lastScanRange.first, we have scanned one block, thus the offsets
val blocksScanned = (lastScannedHeight - lastScanRange.first + 1).coerceAtLeast(0)
// we scan the range inclusively so 100..100 is one block to scan, thus the offset
val numberOfBlocks = lastScanRange.last - lastScanRange.first + 1
// take the percentage then convert and round
((blocksScanned.toFloat() / numberOfBlocks) * 100.0f).let { percent ->
percent.coerceAtMost(100.0f).roundToInt()
}
}
}
}
}