Revamped demo app.
- Add logging - Add service to mainactivity - Use new SDK derivation tool - Add prev/next buttons to block demo - Add helpful stats to block range demo - Add instructions to home screen and stop clearing after each demo - Improve transaction list display - Revamped every demo - Added HTML to displays - Added helpful extension functions - Updated dependencies
This commit is contained in:
parent
09f08bdd04
commit
3e6355b0c7
|
@ -49,8 +49,8 @@ android {
|
|||
|
||||
dependencies {
|
||||
// SDK
|
||||
zcashmainnetImplementation 'cash.z.ecc.android:sdk-mainnet:1.1.0-beta04'
|
||||
zcashtestnetImplementation 'cash.z.ecc.android:sdk-testnet:1.1.0-beta04'
|
||||
zcashmainnetImplementation 'cash.z.ecc.android:sdk-mainnet:1.1.0-beta05'
|
||||
zcashtestnetImplementation 'cash.z.ecc.android:sdk-testnet:1.1.0-beta05'
|
||||
|
||||
// sample mnemonic plugin
|
||||
implementation 'com.github.zcash:zcash-android-wallet-plugins:1.0.1'
|
||||
|
@ -68,20 +68,20 @@ dependencies {
|
|||
implementation 'com.google.guava:guava:27.0.1-android'
|
||||
kapt 'androidx.room:room-compiler:2.2.5'
|
||||
// SDK: Other
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.8'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
|
||||
|
||||
// Android
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
implementation 'androidx.core:core-ktx:1.3.1'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
|
||||
implementation 'androidx.lifecycle:lifecycle-common-java8:2.3.0-alpha06'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-alpha06' // provides lifecycleScope!
|
||||
implementation 'androidx.lifecycle:lifecycle-common-java8:2.3.0-alpha07'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0-alpha07' // provides lifecycleScope!
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.3.0'
|
||||
implementation "com.google.android.material:material:1.3.0-alpha02"
|
||||
testImplementation 'junit:junit:4.13'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package cash.z.ecc.android.sdk.demoapp
|
||||
|
||||
import android.app.Application
|
||||
import cash.z.ecc.android.sdk.ext.TroubleshootingTwig
|
||||
import cash.z.ecc.android.sdk.ext.Twig
|
||||
|
||||
class App : Application() {
|
||||
|
||||
|
@ -9,6 +11,7 @@ class App : Application() {
|
|||
override fun onCreate() {
|
||||
instance = this
|
||||
super.onCreate()
|
||||
Twig.plant(TroubleshootingTwig())
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -11,21 +11,24 @@ import android.widget.Toast
|
|||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import cash.z.ecc.android.sdk.demoapp.util.mainActivity
|
||||
import cash.z.ecc.android.sdk.ext.TroubleshootingTwig
|
||||
import cash.z.ecc.android.sdk.ext.Twig
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
|
||||
abstract class BaseDemoFragment<T : ViewBinding> : Fragment() {
|
||||
|
||||
/**
|
||||
* Since the lightwalletservice is not a component that apps typically use, directly, we provide
|
||||
* this from one place. Everything that can be done with the service can/should be done with the
|
||||
* synchronizer because it wraps the service.
|
||||
*/
|
||||
val lightwalletService get() = mainActivity()?.lightwalletService
|
||||
|
||||
// contains view information provided by the user
|
||||
val sharedViewModel: SharedViewModel by activityViewModels()
|
||||
lateinit var binding: T
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Twig.plant(TroubleshootingTwig())
|
||||
}
|
||||
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
@ -74,11 +77,11 @@ abstract class BaseDemoFragment<T : ViewBinding> : Fragment() {
|
|||
/**
|
||||
* Convenience function to the given text to the clipboard.
|
||||
*/
|
||||
open fun copyToClipboard(text: String) {
|
||||
open fun copyToClipboard(text: String, description: String = "Copied to clipboard!") {
|
||||
(activity?.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)?.let { cm ->
|
||||
cm.setPrimaryClip(ClipData.newPlainText("DemoAppClip", text))
|
||||
}
|
||||
toast("Copied to clipboard!")
|
||||
toast(description)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,6 +17,8 @@ import androidx.navigation.ui.navigateUp
|
|||
import androidx.navigation.ui.setupActionBarWithNavController
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import cash.z.ecc.android.sdk.service.LightWalletGrpcService
|
||||
import cash.z.ecc.android.sdk.service.LightWalletService
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.google.android.material.navigation.NavigationView
|
||||
|
||||
|
@ -27,6 +29,15 @@ class MainActivity : AppCompatActivity(), ClipboardManager.OnPrimaryClipChangedL
|
|||
private var clipboardListener: ((String?) -> Unit)? = null
|
||||
var fabListener: BaseDemoFragment<out ViewBinding>? = null
|
||||
|
||||
/**
|
||||
* The service to use for all demos that interact directly with the service. Since gRPC channels
|
||||
* are expensive to recreate, we set this up once per demo. A real app would hardly ever use
|
||||
* this object because it would utilize the synchronizer, instead, which exposes APIs that
|
||||
* automatically sync with the server.
|
||||
*/
|
||||
var lightwalletService: LightWalletService? = null
|
||||
private set
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
|
@ -54,12 +65,15 @@ class MainActivity : AppCompatActivity(), ClipboardManager.OnPrimaryClipChangedL
|
|||
setupActionBarWithNavController(navController, appBarConfiguration)
|
||||
navView.setupWithNavController(navController)
|
||||
drawerLayout.addDrawerListener(this)
|
||||
|
||||
initService()
|
||||
}
|
||||
|
||||
private fun onFabClicked(view: View) {
|
||||
fabListener?.onActionButtonClicked()
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
lightwalletService?.shutdown()
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
|
@ -82,6 +96,24 @@ class MainActivity : AppCompatActivity(), ClipboardManager.OnPrimaryClipChangedL
|
|||
}
|
||||
|
||||
|
||||
//
|
||||
// Private functions
|
||||
//
|
||||
|
||||
private fun initService() {
|
||||
if (lightwalletService != null) {
|
||||
lightwalletService?.shutdown()
|
||||
}
|
||||
with(App.instance.defaultConfig) {
|
||||
lightwalletService = LightWalletGrpcService(App.instance, host, port)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onFabClicked(view: View) {
|
||||
fabListener?.onActionButtonClicked()
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Helpers
|
||||
//
|
||||
|
|
|
@ -4,10 +4,9 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import cash.z.ecc.android.bip39.Mnemonics
|
||||
import cash.z.ecc.android.bip39.toSeed
|
||||
import cash.z.ecc.android.sdk.Initializer
|
||||
import cash.z.ecc.android.sdk.demoapp.App
|
||||
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
|
||||
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetAddressBinding
|
||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||
|
||||
/**
|
||||
* Displays the address associated with the seed defined by the default config. To modify the seed
|
||||
|
@ -15,7 +14,6 @@ import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetAddressBinding
|
|||
*/
|
||||
class GetAddressFragment : BaseDemoFragment<FragmentGetAddressBinding>() {
|
||||
|
||||
private lateinit var initializer: Initializer
|
||||
private lateinit var viewingKey: String
|
||||
private lateinit var seed: ByteArray
|
||||
|
||||
|
@ -23,7 +21,7 @@ class GetAddressFragment : BaseDemoFragment<FragmentGetAddressBinding>() {
|
|||
* Initialize the required values that would normally live outside the demo but are repeated
|
||||
* here for completeness so that each demo file can serve as a standalone example.
|
||||
*/
|
||||
fun setup() {
|
||||
private fun setup() {
|
||||
// defaults to the value of `DemoConfig.seedWords` but can also be set by the user
|
||||
var seedPhrase = sharedViewModel.seedPhrase.value
|
||||
|
||||
|
@ -31,17 +29,16 @@ class GetAddressFragment : BaseDemoFragment<FragmentGetAddressBinding>() {
|
|||
// have the seed stored
|
||||
seed = Mnemonics.MnemonicCode(seedPhrase).toSeed()
|
||||
|
||||
// the initializer loads rust libraries and helps with configuration
|
||||
initializer = Initializer(App.instance)
|
||||
|
||||
// demonstrate deriving viewing keys for five accounts but only take the first one
|
||||
viewingKey = initializer.deriveViewingKeys(seed).first()
|
||||
// the derivation tool can be used for generating keys and addresses
|
||||
viewingKey = DerivationTool.deriveViewingKeys(seed).first()
|
||||
}
|
||||
|
||||
fun displayAddress() {
|
||||
private fun displayAddress() {
|
||||
// alternatively, `deriveAddress` can take the seed as a parameter instead
|
||||
val address = initializer.deriveAddress(viewingKey)
|
||||
binding.textInfo.text = address
|
||||
// although, a full fledged app would just get the address from the synchronizer
|
||||
val zaddress = DerivationTool.deriveShieldedAddress(viewingKey)
|
||||
val taddress = DerivationTool.deriveTransparentAddress(seed)
|
||||
binding.textInfo.text = "z-addr:\n$zaddress\n\n\nt-addr:\n$taddress"
|
||||
}
|
||||
|
||||
// TODO: show an example with the synchronizer
|
||||
|
@ -66,7 +63,10 @@ class GetAddressFragment : BaseDemoFragment<FragmentGetAddressBinding>() {
|
|||
//
|
||||
|
||||
override fun onActionButtonClicked() {
|
||||
copyToClipboard(initializer.deriveAddress(viewingKey))
|
||||
copyToClipboard(
|
||||
DerivationTool.deriveTransparentAddress(seed),
|
||||
"Shielded address copied to clipboard!"
|
||||
)
|
||||
}
|
||||
|
||||
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentGetAddressBinding =
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
package cash.z.ecc.android.sdk.demoapp.demos.getblock
|
||||
|
||||
import android.os.Bundle
|
||||
import android.text.Html
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import cash.z.ecc.android.sdk.demoapp.App
|
||||
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
|
||||
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetBlockBinding
|
||||
import cash.z.ecc.android.sdk.service.LightWalletGrpcService
|
||||
import cash.z.ecc.android.sdk.service.LightWalletService
|
||||
import cash.z.ecc.android.sdk.demoapp.util.mainActivity
|
||||
import cash.z.ecc.android.sdk.demoapp.util.toHtml
|
||||
import cash.z.ecc.android.sdk.demoapp.util.toRelativeTime
|
||||
import cash.z.ecc.android.sdk.demoapp.util.withCommas
|
||||
import cash.z.ecc.android.sdk.ext.toHex
|
||||
|
||||
/**
|
||||
* Retrieves a compact block from the lightwalletd service and displays basic information about it.
|
||||
|
@ -14,39 +18,60 @@ import cash.z.ecc.android.sdk.service.LightWalletService
|
|||
* the response.
|
||||
*/
|
||||
class GetBlockFragment : BaseDemoFragment<FragmentGetBlockBinding>() {
|
||||
private val host = App.instance.defaultConfig.host
|
||||
private val port = App.instance.defaultConfig.port
|
||||
|
||||
private lateinit var lightwalletService: LightWalletService
|
||||
|
||||
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentGetBlockBinding =
|
||||
FragmentGetBlockBinding.inflate(layoutInflater)
|
||||
|
||||
fun resetInBackground() {
|
||||
lightwalletService = LightWalletGrpcService(App.instance, host, port)
|
||||
}
|
||||
|
||||
fun onResetComplete() {
|
||||
binding.buttonApply.setOnClickListener(::onApply)
|
||||
onApply(binding.textBlockHeight)
|
||||
}
|
||||
|
||||
private fun onApply(_unused: View) {
|
||||
setBlockHeight(binding.textBlockHeight.text.toString().toInt())
|
||||
}
|
||||
|
||||
private fun setBlockHeight(blockHeight: Int) {
|
||||
val blocks =
|
||||
lightwalletService.getBlockRange(blockHeight..blockHeight)
|
||||
val block = blocks.firstOrNull()
|
||||
binding.textInfo.text = """
|
||||
block height: ${block?.height}
|
||||
block vtxCount: ${block?.vtxCount}
|
||||
block time: ${block?.time}
|
||||
lightwalletService?.getBlockRange(blockHeight..blockHeight)
|
||||
val block = blocks?.firstOrNull()
|
||||
binding.textInfo.visibility = View.VISIBLE
|
||||
binding.textInfo.text = Html.fromHtml(
|
||||
"""
|
||||
<b>block height:</b> ${block?.height.withCommas()}
|
||||
<br/><b>block time:</b> ${block?.time.toRelativeTime()}
|
||||
<br/><b>number of shielded TXs:</b> ${block?.vtxCount}
|
||||
<br/><b>hash:</b> ${block?.hash?.toByteArray()?.toHex()}
|
||||
<br/><b>prevHash:</b> ${block?.prevHash?.toByteArray()?.toHex()}
|
||||
${block?.vtxList.toHtml()}
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
fun onClear() {
|
||||
lightwalletService.shutdown()
|
||||
private fun onApply(_unused: View? = null) {
|
||||
try {
|
||||
setBlockHeight(binding.textBlockHeight.text.toString().toInt())
|
||||
} catch (t: Throwable) {
|
||||
toast("Error: $t")
|
||||
}
|
||||
mainActivity()?.hideKeyboard()
|
||||
}
|
||||
|
||||
private fun loadNext(offset: Int) {
|
||||
val nextBlockHeight = (binding.textBlockHeight.text.toString().toIntOrNull() ?: -1) + offset
|
||||
binding.textBlockHeight.setText(nextBlockHeight.toString())
|
||||
onApply()
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Android Lifecycle overrides
|
||||
//
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding.buttonApply.setOnClickListener(::onApply)
|
||||
binding.buttonPrevious.setOnClickListener {
|
||||
loadNext(-1)
|
||||
}
|
||||
binding.buttonNext.setOnClickListener {
|
||||
loadNext(1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Base Fragment overrides
|
||||
//
|
||||
|
||||
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentGetBlockBinding =
|
||||
FragmentGetBlockBinding.inflate(layoutInflater)
|
||||
}
|
||||
|
|
|
@ -1,65 +1,113 @@
|
|||
package cash.z.ecc.android.sdk.demoapp.demos.getblockrange
|
||||
|
||||
import android.os.Bundle
|
||||
import android.text.Html
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import cash.z.ecc.android.sdk.demoapp.App
|
||||
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
|
||||
import cash.z.ecc.android.sdk.demoapp.R
|
||||
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetBlockRangeBinding
|
||||
import cash.z.ecc.android.sdk.service.LightWalletGrpcService
|
||||
import cash.z.ecc.android.sdk.service.LightWalletService
|
||||
import cash.z.ecc.android.sdk.demoapp.util.mainActivity
|
||||
import cash.z.ecc.android.sdk.demoapp.util.toRelativeTime
|
||||
import cash.z.ecc.android.sdk.demoapp.util.withCommas
|
||||
|
||||
/**
|
||||
* Retrieves a range of compact block from the lightwalletd service and displays basic information
|
||||
* about them. This demonstrates the basic ability to connect to the server, request a range of
|
||||
* about them. This demonstrates the basic ability to connect to the server, request a range of
|
||||
* compact block and parse the response. This could be augmented to display metadata about certain
|
||||
* block ranges for instance, to find the block with the most shielded transactions in a range.
|
||||
*/
|
||||
class GetBlockRangeFragment : BaseDemoFragment<FragmentGetBlockRangeBinding>() {
|
||||
|
||||
private val host = App.instance.defaultConfig.host
|
||||
private val port = App.instance.defaultConfig.port
|
||||
private fun setBlockRange(blockRange: IntRange) {
|
||||
val start = System.currentTimeMillis()
|
||||
val blocks =
|
||||
lightwalletService?.getBlockRange(blockRange)
|
||||
val fetchDelta = System.currentTimeMillis() - start
|
||||
|
||||
private lateinit var lightwalletService: LightWalletService
|
||||
|
||||
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentGetBlockRangeBinding =
|
||||
FragmentGetBlockRangeBinding.inflate(layoutInflater)
|
||||
// Note: This is a demo so we won't worry about iterating efficiently over these blocks
|
||||
|
||||
fun resetInBackground() {
|
||||
lightwalletService = LightWalletGrpcService(App.instance, host, port)
|
||||
}
|
||||
binding.textInfo.text = Html.fromHtml(blocks?.run {
|
||||
val count = size
|
||||
val emptyCount = count { it.vtxCount == 0 }
|
||||
val maxTxs = maxByOrNull { it.vtxCount }
|
||||
val maxIns = maxByOrNull { block ->
|
||||
block.vtxList.maxOfOrNull { it.spendsCount } ?: -1
|
||||
}
|
||||
val maxInTx = maxIns?.vtxList?.maxByOrNull { it.spendsCount }
|
||||
val maxOuts = maxByOrNull { block ->
|
||||
block.vtxList.maxOfOrNull { it.outputsCount } ?: -1
|
||||
}
|
||||
val maxOutTx = maxOuts?.vtxList?.maxByOrNull { it.outputsCount }
|
||||
val txCount = sumBy { it.vtxCount }
|
||||
val outCount = sumBy { block -> block.vtxList.sumBy { it.outputsCount } }
|
||||
val inCount = sumBy { block -> block.vtxList.sumBy { it.spendsCount } }
|
||||
|
||||
fun onResetComplete() {
|
||||
binding.buttonApply.setOnClickListener(::onApply)
|
||||
onApply(binding.textInfo)
|
||||
}
|
||||
|
||||
fun onClear() {
|
||||
lightwalletService.shutdown()
|
||||
val processTime = System.currentTimeMillis() - start - fetchDelta
|
||||
"""
|
||||
<b>total blocks:</b> ${count.withCommas()}
|
||||
<br/><b>fetch time:</b> ${if(fetchDelta > 1000) "%.2f sec".format(fetchDelta/1000.0) else "%d ms".format(fetchDelta)}
|
||||
<br/><b>process time:</b> ${if(processTime > 1000) "%.2f sec".format(processTime/1000.0) else "%d ms".format(processTime)}
|
||||
<br/><b>block time range:</b> ${first().time.toRelativeTime()}<br/>   to ${last().time.toRelativeTime()}
|
||||
<br/><b>total empty blocks:</b> ${emptyCount.withCommas()}
|
||||
<br/><b>total TXs:</b> ${txCount.withCommas()}
|
||||
<br/><b>total outputs:</b> ${outCount.withCommas()}
|
||||
<br/><b>total inputs:</b> ${inCount.withCommas()}
|
||||
<br/><b>avg TXs/block:</b> ${"%.1f".format(txCount/count.toDouble())}
|
||||
<br/><b>avg TXs (excluding empty blocks):</b> ${"%.1f".format(txCount.toDouble()/(count - emptyCount))}
|
||||
<br/><b>avg OUTs [per block / per TX]:</b> ${"%.1f / %.1f".format(outCount.toDouble()/(count - emptyCount), outCount.toDouble()/txCount)}
|
||||
<br/><b>avg INs [per block / per TX]:</b> ${"%.1f / %.1f".format(inCount.toDouble()/(count - emptyCount), inCount.toDouble()/txCount)}
|
||||
<br/><b>most shielded TXs:</b> ${if(maxTxs==null) "none" else "${maxTxs.vtxCount} in block ${maxTxs.height.withCommas()}"}
|
||||
<br/><b>most shielded INs:</b> ${if(maxInTx==null) "none" else "${maxInTx.spendsCount} in block ${maxIns?.height.withCommas()} at tx index ${maxInTx.index}"}
|
||||
<br/><b>most shielded OUTs:</b> ${if(maxOutTx==null) "none" else "${maxOutTx?.outputsCount} in block ${maxOuts?.height.withCommas()} at tx index ${maxOutTx?.index}"}
|
||||
""".trimIndent()
|
||||
} ?: "No blocks found in that range.")
|
||||
}
|
||||
|
||||
private fun onApply(_unused: View) {
|
||||
val start = binding.textStartHeight.text.toString().toInt()
|
||||
val end = binding.textEndHeight.text.toString().toInt()
|
||||
if (start <= end) {
|
||||
setBlockRange(start..end)
|
||||
try {
|
||||
with(binding.buttonApply) {
|
||||
isEnabled = false
|
||||
setText(R.string.loading)
|
||||
binding.textInfo.setText(R.string.loading)
|
||||
post {
|
||||
setBlockRange(start..end)
|
||||
isEnabled = true
|
||||
setText(R.string.apply)
|
||||
}
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
setError(t.toString())
|
||||
}
|
||||
} else {
|
||||
setError("Invalid range")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: iterate on this demo to show all the blocks in a recyclerview showing block heights and vtx count
|
||||
private fun setBlockRange(blockRange: IntRange) {
|
||||
val blocks =
|
||||
lightwalletService.getBlockRange(blockRange)
|
||||
val block = blocks.firstOrNull()
|
||||
binding.textInfo.text = """
|
||||
block height: ${block?.height}
|
||||
block vtxCount: ${block?.vtxCount}
|
||||
block time: ${block?.time}
|
||||
""".trimIndent()
|
||||
mainActivity()?.hideKeyboard()
|
||||
}
|
||||
|
||||
private fun setError(message: String) {
|
||||
binding.textInfo.text = "Error: $message"
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Android Lifecycle overrides
|
||||
//
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
binding.buttonApply.setOnClickListener(::onApply)
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Base Fragment overrides
|
||||
//
|
||||
|
||||
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentGetBlockRangeBinding =
|
||||
FragmentGetBlockRangeBinding.inflate(layoutInflater)
|
||||
|
||||
}
|
||||
|
|
|
@ -1,41 +1,38 @@
|
|||
package cash.z.ecc.android.sdk.demoapp.demos.getlatestheight
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import cash.z.ecc.android.sdk.demoapp.App
|
||||
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
|
||||
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetLatestHeightBinding
|
||||
import cash.z.ecc.android.sdk.service.LightWalletGrpcService
|
||||
import cash.z.ecc.android.sdk.service.LightWalletService
|
||||
|
||||
/**
|
||||
* Retrieves the latest block height from the lightwalletd server. This is the simplest test for
|
||||
* connectivity with the server. Modify the `host` and the `port` to check the SDK's ability to
|
||||
* communicate with a given lightwalletd instance.
|
||||
* connectivity with the server. Modify the `host` and the `port` inside of
|
||||
* `App.instance.defaultConfig` to check the SDK's ability to communicate with a given lightwalletd
|
||||
* instance.
|
||||
*/
|
||||
class GetLatestHeightFragment : BaseDemoFragment<FragmentGetLatestHeightBinding>() {
|
||||
private val host = App.instance.defaultConfig.host
|
||||
private val port = App.instance.defaultConfig.port
|
||||
|
||||
private lateinit var lightwalletService: LightWalletService
|
||||
private fun displayLatestHeight() {
|
||||
// note: this is a blocking call, a real app wouldn't do this on the main thread
|
||||
// instead, a production app would leverage the synchronizer like in the other demos
|
||||
binding.textInfo.text = lightwalletService?.getLatestBlockHeight().toString()
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Android Lifecycle overrides
|
||||
//
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
displayLatestHeight()
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Base Fragment overrides
|
||||
//
|
||||
|
||||
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentGetLatestHeightBinding =
|
||||
FragmentGetLatestHeightBinding.inflate(layoutInflater)
|
||||
|
||||
fun resetInBackground() {
|
||||
lightwalletService = LightWalletGrpcService(App.instance, host, port)
|
||||
}
|
||||
|
||||
fun onResetComplete() {
|
||||
binding.textInfo.text = lightwalletService.getLatestBlockHeight().toString()
|
||||
}
|
||||
|
||||
fun onClear() {
|
||||
lightwalletService.shutdown()
|
||||
}
|
||||
|
||||
override fun onActionButtonClicked() {
|
||||
toast("Refreshed!")
|
||||
onResetComplete()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,9 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import cash.z.ecc.android.bip39.Mnemonics
|
||||
import cash.z.ecc.android.bip39.toSeed
|
||||
import cash.z.ecc.android.sdk.Initializer
|
||||
import cash.z.ecc.android.sdk.demoapp.App
|
||||
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
|
||||
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetPrivateKeyBinding
|
||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||
|
||||
/**
|
||||
* Displays the viewing key and spending key associated with the seed used during the demo. The
|
||||
|
@ -16,7 +15,6 @@ import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetPrivateKeyBinding
|
|||
*/
|
||||
class GetPrivateKeyFragment : BaseDemoFragment<FragmentGetPrivateKeyBinding>() {
|
||||
|
||||
private lateinit var initializer: Initializer
|
||||
private lateinit var seedPhrase: String
|
||||
private lateinit var seed: ByteArray
|
||||
|
||||
|
@ -24,10 +22,7 @@ class GetPrivateKeyFragment : BaseDemoFragment<FragmentGetPrivateKeyBinding>() {
|
|||
* Initialize the required values that would normally live outside the demo but are repeated
|
||||
* here for completeness so that each demo file can serve as a standalone example.
|
||||
*/
|
||||
fun setup() {
|
||||
// the initializer loads rust libraries and helps with configuration
|
||||
initializer = Initializer(App.instance)
|
||||
|
||||
private fun setup() {
|
||||
// defaults to the value of `DemoConfig.seedWords` but can also be set by the user
|
||||
seedPhrase = sharedViewModel.seedPhrase.value
|
||||
|
||||
|
@ -36,11 +31,13 @@ class GetPrivateKeyFragment : BaseDemoFragment<FragmentGetPrivateKeyBinding>() {
|
|||
seed = Mnemonics.MnemonicCode(seedPhrase).toSeed()
|
||||
}
|
||||
|
||||
fun displayKeys() {
|
||||
private fun displayKeys() {
|
||||
// derive the keys from the seed:
|
||||
// demonstrate deriving spending keys for five accounts but only take the first one
|
||||
val spendingKey = initializer.deriveSpendingKeys(seed, 5).first()
|
||||
val viewingKey = initializer.deriveViewingKey(spendingKey)
|
||||
val spendingKey = DerivationTool.deriveSpendingKeys(seed, 5).first()
|
||||
|
||||
// derive the key that allows you to view but not spend transactions
|
||||
val viewingKey = DerivationTool.deriveViewingKey(spendingKey)
|
||||
|
||||
// display the keys in the UI
|
||||
binding.textInfo.setText("Spending Key:\n$spendingKey\n\nViewing Key:\n$viewingKey")
|
||||
|
@ -66,6 +63,13 @@ class GetPrivateKeyFragment : BaseDemoFragment<FragmentGetPrivateKeyBinding>() {
|
|||
// Base Fragment overrides
|
||||
//
|
||||
|
||||
override fun onActionButtonClicked() {
|
||||
copyToClipboard(
|
||||
DerivationTool.deriveViewingKeys(seed, 1).first(),
|
||||
"ViewingKey copied to clipboard!"
|
||||
)
|
||||
}
|
||||
|
||||
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentGetPrivateKeyBinding =
|
||||
FragmentGetPrivateKeyBinding.inflate(layoutInflater)
|
||||
|
||||
|
|
|
@ -41,12 +41,6 @@ class HomeFragment : BaseDemoFragment<FragmentHomeBinding>() {
|
|||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
twig(
|
||||
"CLEARING DATA: Visiting the home screen clears the default databases, for sanity" +
|
||||
" sake, because each demo is intended to be self-contained."
|
||||
)
|
||||
// App.instance.getDatabasePath("unusued.db").parentFile?.listFiles()?.forEach { it.delete() }
|
||||
|
||||
mainActivity()?.setClipboardListener(::updatePasteButton)
|
||||
|
||||
lifecycleScope.launch {
|
||||
|
@ -101,6 +95,7 @@ class HomeFragment : BaseDemoFragment<FragmentHomeBinding>() {
|
|||
private fun setEditShown(isShown: Boolean) {
|
||||
with(binding) {
|
||||
textSeedPhrase.visibility = if (isShown) View.GONE else View.VISIBLE
|
||||
textInstructions.visibility = if (isShown) View.GONE else View.VISIBLE
|
||||
groupEdit.visibility = if (isShown) View.VISIBLE else View.GONE
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,22 @@
|
|||
package cash.z.ecc.android.sdk.demoapp.demos.listtransactions
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.paging.PagedList
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import cash.z.ecc.android.sdk.Initializer
|
||||
import cash.z.ecc.android.sdk.SdkSynchronizer
|
||||
import cash.z.ecc.android.sdk.Synchronizer
|
||||
import cash.z.ecc.android.sdk.VkInitializer
|
||||
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
|
||||
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
|
||||
import cash.z.ecc.android.sdk.demoapp.App
|
||||
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
|
||||
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentListTransactionsBinding
|
||||
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
|
||||
import cash.z.ecc.android.sdk.ext.collectWith
|
||||
import cash.z.ecc.android.sdk.ext.twig
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||
|
||||
/**
|
||||
* List all transactions related to the given seed, since the given birthday. This begins by
|
||||
|
@ -26,60 +26,46 @@ import kotlinx.coroutines.launch
|
|||
* database in a paged format that works natively with RecyclerViews.
|
||||
*/
|
||||
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.loadBirthday()
|
||||
private lateinit var initializer: SdkSynchronizer.SdkInitializer
|
||||
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)
|
||||
|
||||
fun resetInBackground() {
|
||||
initializer.new(config.seed, birthday)
|
||||
/**
|
||||
* Initialize the required values that would normally live outside the demo but are repeated
|
||||
* here for completeness so that each demo file can serve as a standalone example.
|
||||
*/
|
||||
private fun setup() {
|
||||
App.instance.defaultConfig.let { config ->
|
||||
initializer = VkInitializer(App.instance) { import(config.seed, config.birthdayHeight) }
|
||||
address = DerivationTool.deriveShieldedAddress(config.seed)
|
||||
}
|
||||
synchronizer = Synchronizer(initializer)
|
||||
}
|
||||
|
||||
fun onResetComplete() {
|
||||
initTransactionUI()
|
||||
startSynchronizer()
|
||||
monitorStatus()
|
||||
}
|
||||
|
||||
fun onClear() {
|
||||
synchronizer.stop()
|
||||
initializer.clear()
|
||||
}
|
||||
|
||||
private fun initTransactionUI() {
|
||||
binding.recyclerTransactions.layoutManager =
|
||||
LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false)
|
||||
adapter = TransactionAdapter()
|
||||
lifecycleScope.launch {
|
||||
address = synchronizer.getAddress()
|
||||
synchronizer.receivedTransactions.onEach {
|
||||
onTransactionsUpdated(it)
|
||||
}.launchIn(this)
|
||||
}
|
||||
binding.recyclerTransactions.adapter = adapter
|
||||
}
|
||||
|
||||
private fun startSynchronizer() {
|
||||
lifecycleScope.apply {
|
||||
synchronizer.start(this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun monitorStatus() {
|
||||
private fun monitorChanges() {
|
||||
// the lifecycleScope is used to stop everything when the fragment dies
|
||||
synchronizer.status.collectWith(lifecycleScope, ::onStatus)
|
||||
synchronizer.processorInfo.collectWith(lifecycleScope, ::onProcessorInfoUpdated)
|
||||
synchronizer.progress.collectWith(lifecycleScope, ::onProgress)
|
||||
synchronizer.clearedTransactions.collectWith(lifecycleScope, ::onTransactionsUpdated)
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Change listeners
|
||||
//
|
||||
|
||||
private fun onProcessorInfoUpdated(info: CompactBlockProcessor.ProcessorInfo) {
|
||||
if (info.isScanning) binding.textInfo.text = "Scanning blocks...${info.scanProgress}%"
|
||||
}
|
||||
|
@ -108,8 +94,8 @@ class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBindin
|
|||
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"
|
||||
"No transactions found. Try to either change the seed words " +
|
||||
"or send funds to this address (tap the FAB to copy it):\n\n $address"
|
||||
} else {
|
||||
visibility = View.INVISIBLE
|
||||
text = ""
|
||||
|
@ -118,7 +104,38 @@ class ListTransactionsFragment : BaseDemoFragment<FragmentListTransactionsBindin
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Android Lifecycle overrides
|
||||
//
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
setup()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
initTransactionUI()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
// the lifecycleScope is used to dispose of the synchronizer when the fragment dies
|
||||
synchronizer.start(lifecycleScope)
|
||||
monitorChanges()
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Base Fragment overrides
|
||||
//
|
||||
|
||||
override fun onActionButtonClicked() {
|
||||
if (::address.isInitialized) copyToClipboard(address)
|
||||
}
|
||||
|
||||
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentListTransactionsBinding =
|
||||
FragmentListTransactionsBinding.inflate(layoutInflater)
|
||||
|
||||
}
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
package cash.z.ecc.android.sdk.demoapp.demos.listtransactions
|
||||
|
||||
import android.content.res.ColorStateList
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.ColorRes
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import cash.z.ecc.android.sdk.demoapp.R
|
||||
import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
|
||||
import cash.z.ecc.android.sdk.demoapp.App
|
||||
import cash.z.ecc.android.sdk.demoapp.R
|
||||
import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
@ -16,14 +22,20 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
|||
private val amountText = itemView.findViewById<TextView>(R.id.text_transaction_amount)
|
||||
private val infoText = itemView.findViewById<TextView>(R.id.text_transaction_info)
|
||||
private val timeText = itemView.findViewById<TextView>(R.id.text_transaction_timestamp)
|
||||
private val icon = itemView.findViewById<ImageView>(R.id.image_transaction_type)
|
||||
private val formatter = SimpleDateFormat("M/d h:mma", Locale.getDefault())
|
||||
|
||||
fun bindTo(transaction: T?) {
|
||||
val isInbound = transaction?.toAddress.isNullOrEmpty()
|
||||
amountText.text = transaction?.value.convertZatoshiToZecString()
|
||||
timeText.text =
|
||||
if (transaction == null || transaction?.blockTimeInSeconds == 0L) "Pending"
|
||||
else formatter.format(transaction.blockTimeInSeconds * 1000L)
|
||||
infoText.text = getMemoString(transaction)
|
||||
|
||||
icon.rotation = if (isInbound) 0f else 180f
|
||||
icon.rotation = if (isInbound) 0f else 180f
|
||||
icon.setColorFilter(ContextCompat.getColor(App.instance, if (isInbound) R.color.tx_inbound else R.color.tx_outbound))
|
||||
}
|
||||
|
||||
private fun getMemoString(transaction: T?): String {
|
||||
|
|
|
@ -24,11 +24,11 @@ import cash.z.ecc.android.sdk.ext.twig
|
|||
import cash.z.ecc.android.sdk.service.LightWalletGrpcService
|
||||
import cash.z.ecc.android.sdk.service.LightWalletService
|
||||
import cash.z.ecc.android.sdk.rpc.LocalRpcTypes
|
||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* List all transactions related to the given seed, since the given birthday. This begins by
|
||||
* downloading any missing blocks and then validating and scanning their contents. Once scan is
|
||||
|
@ -66,8 +66,6 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
|
|||
initTransactionUi()
|
||||
}
|
||||
|
||||
private lateinit var lightwalletService: LightWalletService
|
||||
|
||||
fun downloadTransactions() {
|
||||
|
||||
binding.textStatus.text = "loading..."
|
||||
|
@ -76,14 +74,13 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
|
|||
val addressToUse = binding.inputAddress.text.toString()
|
||||
val startToUse = binding.inputRangeStart.text.toString().toIntOrNull() ?: ZcashSdk.SAPLING_ACTIVATION_HEIGHT
|
||||
val endToUse = binding.inputRangeEnd.text.toString().toIntOrNull() ?: latestBlockHeight
|
||||
lightwalletService = LightWalletGrpcService(App.instance, config.host, config.port)
|
||||
var allStart = now
|
||||
twig("loading transactions in range $startToUse..$endToUse")
|
||||
val txids = lightwalletService.getTAddressTransactions(addressToUse, startToUse..endToUse)
|
||||
val txids = lightwalletService?.getTAddressTransactions(addressToUse, startToUse..endToUse)
|
||||
var delta = now - allStart
|
||||
updateStatus("found ${txids.size} transactions in ${delta}ms.", false)
|
||||
updateStatus("found ${txids?.size} transactions in ${delta}ms.", false)
|
||||
|
||||
txids.map {
|
||||
txids?.map {
|
||||
it.data.apply {
|
||||
try {
|
||||
initializer.rustBackend.decryptAndStoreTransaction(toByteArray())
|
||||
|
@ -91,7 +88,7 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
|
|||
twig("failed to decrypt and store transaction due to: $t")
|
||||
}
|
||||
}
|
||||
}.let { txData ->
|
||||
}?.let { txData ->
|
||||
val parseStart = now
|
||||
val tList = LocalRpcTypes.TransactionDataList.newBuilder().addAllData(txData).build()
|
||||
val parsedTransactions = initializer.rustBackend.parseTransactionDataList(tList)
|
||||
|
@ -136,7 +133,7 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
|
|||
super.onResume()
|
||||
resetInBackground()
|
||||
val seed = Mnemonics.MnemonicCode(sharedViewModel.seedPhrase.value).toSeed()
|
||||
binding.inputAddress.setText(initializer.rustBackend.deriveTAddress(seed))
|
||||
binding.inputAddress.setText(DerivationTool.deriveTransparentAddress(seed))
|
||||
}
|
||||
|
||||
|
||||
|
@ -226,13 +223,6 @@ class ListUtxosFragment : BaseDemoFragment<FragmentListUtxosBinding>() {
|
|||
private fun onTransactionsUpdated(transactions: PagedList<ConfirmedTransaction>) {
|
||||
twig("got a new paged list of transactions of size ${transactions.size}")
|
||||
adapter.submitList(transactions)
|
||||
|
||||
// show message when there are no transactions
|
||||
// if (isSynced) {
|
||||
if (transactions.isEmpty()) {
|
||||
Toast.makeText(activity, "No transactions found. Try another address or send funds to this one.", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
override fun onActionButtonClicked() {
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
package cash.z.ecc.android.sdk.demoapp.demos.send
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import cash.z.ecc.android.sdk.Initializer
|
||||
import cash.z.ecc.android.sdk.Synchronizer
|
||||
import cash.z.ecc.android.sdk.VkInitializer
|
||||
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
|
||||
import cash.z.ecc.android.sdk.db.entity.*
|
||||
import cash.z.ecc.android.sdk.demoapp.App
|
||||
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
|
||||
import cash.z.ecc.android.sdk.demoapp.R
|
||||
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentSendBinding
|
||||
import cash.z.ecc.android.sdk.demoapp.util.SampleStorageBridge
|
||||
import cash.z.ecc.android.sdk.db.entity.*
|
||||
import cash.z.ecc.android.sdk.demoapp.util.mainActivity
|
||||
import cash.z.ecc.android.sdk.ext.*
|
||||
import cash.z.ecc.android.sdk.tool.DerivationTool
|
||||
|
||||
/**
|
||||
* Demonstrates sending funds to an address. This is the most complex example that puts all of the
|
||||
|
@ -24,16 +26,30 @@ import cash.z.ecc.android.sdk.ext.*
|
|||
* Any time the state of that transaction changes, a new instance will be emitted.
|
||||
*/
|
||||
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.loadBirthday()
|
||||
|
||||
private lateinit var synchronizer: Synchronizer
|
||||
private lateinit var keyManager: SampleStorageBridge
|
||||
|
||||
private lateinit var amountInput: TextView
|
||||
private lateinit var addressInput: TextView
|
||||
|
||||
// in a normal app, this would be stored securely with the trusted execution environment (TEE)
|
||||
// but since this is a demo, we'll derive it on the fly
|
||||
private lateinit var spendingKey: String
|
||||
|
||||
|
||||
/**
|
||||
* Initialize the required values that would normally live outside the demo but are repeated
|
||||
* here for completeness so that each demo file can serve as a standalone example.
|
||||
*/
|
||||
private fun setup() {
|
||||
App.instance.defaultConfig.let { config ->
|
||||
VkInitializer(App.instance) {
|
||||
import(config.seed, config.birthdayHeight)
|
||||
}.let { initializer ->
|
||||
synchronizer = Synchronizer(initializer)
|
||||
}
|
||||
spendingKey = DerivationTool.deriveSpendingKeys(config.seed).first()
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Observable properties (done without livedata or flows for simplicity)
|
||||
|
@ -57,52 +73,22 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
|
|||
}
|
||||
|
||||
|
||||
//
|
||||
// BaseDemoFragment overrides
|
||||
//
|
||||
|
||||
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentSendBinding =
|
||||
FragmentSendBinding.inflate(layoutInflater)
|
||||
|
||||
fun resetInBackground() {
|
||||
val spendingKeys = initializer.new(config.seed, birthday)
|
||||
keyManager = SampleStorageBridge().securelyStorePrivateKey(spendingKeys[0])
|
||||
synchronizer = Synchronizer(initializer)
|
||||
}
|
||||
|
||||
// STARTING POINT
|
||||
fun onResetComplete() {
|
||||
initSendUi()
|
||||
startSynchronizer()
|
||||
monitorChanges()
|
||||
}
|
||||
|
||||
fun onClear() {
|
||||
synchronizer.stop()
|
||||
initializer.clear()
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Private functions
|
||||
//
|
||||
|
||||
private fun initSendUi() {
|
||||
amountInput = binding.root.findViewById<TextView>(R.id.input_amount).apply {
|
||||
text = config.sendAmount.toString()
|
||||
}
|
||||
addressInput = binding.root.findViewById<TextView>(R.id.input_address).apply {
|
||||
text = config.toAddress
|
||||
App.instance.defaultConfig.let { config ->
|
||||
amountInput = binding.inputAmount.apply {
|
||||
setText(config.sendAmount.toZecString())
|
||||
}
|
||||
addressInput = binding.inputAddress.apply {
|
||||
setText(config.toAddress)
|
||||
}
|
||||
}
|
||||
binding.buttonSend.setOnClickListener(::onSend)
|
||||
}
|
||||
|
||||
private fun startSynchronizer() {
|
||||
lifecycleScope.apply {
|
||||
synchronizer.start(this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun monitorChanges() {
|
||||
synchronizer.status.collectWith(lifecycleScope, ::onStatus)
|
||||
synchronizer.progress.collectWith(lifecycleScope, ::onProgress)
|
||||
|
@ -110,6 +96,11 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
|
|||
synchronizer.balances.collectWith(lifecycleScope, ::onBalance)
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Change listeners
|
||||
//
|
||||
|
||||
private fun onStatus(status: Synchronizer.Status) {
|
||||
binding.textStatus.text = "Status: $status"
|
||||
isSyncing = status != Synchronizer.Status.SYNCED
|
||||
|
@ -148,11 +139,12 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
|
|||
val amount = amountInput.text.toString().toDouble().convertZecToZatoshi()
|
||||
val toAddress = addressInput.text.toString().trim()
|
||||
synchronizer.sendToAddress(
|
||||
keyManager.key,
|
||||
spendingKey,
|
||||
amount,
|
||||
toAddress,
|
||||
"Demo App Funds"
|
||||
"Funds from Demo App"
|
||||
).collectWith(lifecycleScope, ::onPendingTxUpdated)
|
||||
mainActivity()?.hideKeyboard()
|
||||
}
|
||||
|
||||
private fun onPendingTxUpdated(pendingTransaction: PendingTransaction?) {
|
||||
|
@ -197,4 +189,33 @@ class SendFragment : BaseDemoFragment<FragmentSendBinding>() {
|
|||
binding.textInfo.text = "Active Transaction:"
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Android Lifecycle overrides
|
||||
//
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
setup()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
initSendUi()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
// the lifecycleScope is used to dispose of the synchronizer when the fragment dies
|
||||
synchronizer.start(lifecycleScope)
|
||||
monitorChanges()
|
||||
}
|
||||
|
||||
//
|
||||
// BaseDemoFragment overrides
|
||||
//
|
||||
|
||||
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentSendBinding =
|
||||
FragmentSendBinding.inflate(layoutInflater)
|
||||
|
||||
}
|
||||
|
|
|
@ -1,11 +1,46 @@
|
|||
package cash.z.ecc.android.sdk.demoapp.util
|
||||
|
||||
import android.text.format.DateUtils
|
||||
import androidx.fragment.app.Fragment
|
||||
import cash.z.ecc.android.sdk.demoapp.App
|
||||
import cash.z.ecc.android.sdk.demoapp.MainActivity
|
||||
import cash.z.wallet.sdk.rpc.CompactFormats
|
||||
|
||||
/**
|
||||
* Lazy extensions to make demo life easier.
|
||||
*/
|
||||
|
||||
|
||||
fun Fragment.mainActivity() = context as? MainActivity
|
||||
fun Fragment.mainActivity() = context as? MainActivity
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Add locale-specific commas to a number, if it exists.
|
||||
*/
|
||||
fun Number?.withCommas() = this?.let { "%,d".format(it) } ?: "Unknown"
|
||||
|
||||
/**
|
||||
* Convert date time in seconds to relative time like (4 days ago).
|
||||
*/
|
||||
fun Int?.toRelativeTime() =
|
||||
this?.let { timeInSeconds ->
|
||||
DateUtils.getRelativeDateTimeString(
|
||||
App.instance,
|
||||
timeInSeconds * 1000L,
|
||||
DateUtils.SECOND_IN_MILLIS,
|
||||
DateUtils.WEEK_IN_MILLIS,
|
||||
DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_YEAR or DateUtils.FORMAT_ABBREV_MONTH
|
||||
).toString()
|
||||
} ?: "Unknown"
|
||||
|
||||
|
||||
fun List<CompactFormats.CompactTx>?.toHtml() =
|
||||
this.takeUnless { it.isNullOrEmpty() }?.let { txs ->
|
||||
buildString {
|
||||
append("<br/><b>transactions (shielded INs / OUTs):</b>")
|
||||
txs.forEach { append("<br/><b> tx${it.index}:</b> ${it.spendsCount} / ${it.outputsCount}") }
|
||||
}
|
||||
} ?: ""
|
||||
|
||||
|
|
|
@ -10,11 +10,12 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:textSize="20sp"
|
||||
android:textSize="18sp"
|
||||
android:text="loading address..."
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.2"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
@ -9,20 +9,26 @@
|
|||
android:id="@+id/text_layout_block_height"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:endIconMode="clear_text"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/button_apply"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
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.1">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/text_block_height"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="8"
|
||||
android:background="@android:color/transparent"
|
||||
android:ems="10"
|
||||
android:hint="block height"
|
||||
android:imeActionLabel="load"
|
||||
android:imeOptions="actionDone"
|
||||
android:inputType="number"
|
||||
android:maxLines="1"
|
||||
android:text="500000"
|
||||
android:textSize="20sp" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
@ -31,21 +37,48 @@
|
|||
android:id="@+id/button_apply"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="apply"
|
||||
app:layout_constraintBottom_toBottomOf="@id/text_layout_block_height"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/text_layout_block_height"
|
||||
app:layout_constraintTop_toTopOf="@id/text_layout_block_height" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_info"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:text="loading block..."
|
||||
android:textSize="20sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="@string/load"
|
||||
app:layout_constraintEnd_toStartOf="@id/button_previous"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/text_layout_block_height" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_previous"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="@string/previous"
|
||||
app:layout_constraintEnd_toStartOf="@id/button_next"
|
||||
app:layout_constraintStart_toEndOf="@id/button_apply"
|
||||
app:layout_constraintTop_toTopOf="@id/button_apply" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_next"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/next"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/button_previous"
|
||||
app:layout_constraintTop_toTopOf="@id/button_apply" />
|
||||
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:fillViewport="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/button_apply">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:text="loading block..."
|
||||
android:textSize="20sp"
|
||||
android:visibility="gone" />
|
||||
</ScrollView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
@ -14,12 +14,13 @@
|
|||
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.1">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/text_start_height"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent"
|
||||
android:hint="start height"
|
||||
android:inputType="number"
|
||||
android:maxLength="7"
|
||||
|
@ -38,12 +39,13 @@
|
|||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toEndOf="@id/text_layout_start_height"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_bias="0.2">
|
||||
app:layout_constraintVertical_bias="0.1">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/text_end_height"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent"
|
||||
android:hint="end height"
|
||||
android:inputType="number"
|
||||
android:maxLength="7"
|
||||
|
@ -62,15 +64,19 @@
|
|||
app:layout_constraintStart_toEndOf="@id/text_layout_end_height"
|
||||
app:layout_constraintTop_toTopOf="@id/text_layout_end_height" />
|
||||
|
||||
<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_layout_end_height" />
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:fillViewport="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/text_layout_start_height">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:text="loading blocks..."
|
||||
android:textSize="19sp"/>
|
||||
</ScrollView>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:textSize="20sp"
|
||||
android:textSize="18sp"
|
||||
android:text="loading keys soon..."
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:drawableEnd="@drawable/ic_baseline_edit_24"
|
||||
android:drawableTint="@color/colorPrimary"
|
||||
android:drawablePadding="12dp"
|
||||
android:padding="24dp"
|
||||
android:text="Seed phrase set to: apple...fish"
|
||||
|
@ -77,6 +78,20 @@
|
|||
app:layout_constraintEnd_toStartOf="@id/button_accept"
|
||||
app:layout_constraintTop_toBottomOf="@id/text_layout_seed_phrase" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_instructions"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Or modify the seed phrase\nused for all demos, below:"
|
||||
android:gravity="center"
|
||||
android:textAlignment="center"
|
||||
android:textSize="20sp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/text_home"
|
||||
app:layout_constraintBottom_toTopOf="@id/text_seed_phrase"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_home"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
@ -3,12 +3,14 @@
|
|||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
android:padding="16dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/text_layout_amount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/button_send"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
|
@ -20,27 +22,30 @@
|
|||
android:id="@+id/input_amount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent"
|
||||
android:ems="8"
|
||||
android:hint="zec amount"
|
||||
android:inputType="number"
|
||||
android:textSize="20sp" />
|
||||
android:textSize="18sp" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/text_layout_address"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toStartOf="@id/text_layout_amount"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/text_layout_amount">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/input_address"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent"
|
||||
android:maxLines="2"
|
||||
android:ems="8"
|
||||
android:hint="to address"
|
||||
android:textSize="20sp" />
|
||||
android:textSize="18sp" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<Button
|
||||
|
@ -50,9 +55,7 @@
|
|||
android:enabled="false"
|
||||
android:text="Send"
|
||||
app:layout_constraintBottom_toBottomOf="@id/text_layout_amount"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/text_layout_amount"
|
||||
app:layout_constraintTop_toTopOf="@id/text_layout_amount" />
|
||||
app:layout_constraintStart_toEndOf="@id/text_layout_amount"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_status"
|
||||
|
@ -75,17 +78,17 @@
|
|||
<ScrollView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintStart_toStartOf="@id/text_balance"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/text_balance"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
>
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@id/text_balance"
|
||||
app:layout_constraintTop_toBottomOf="@id/text_balance">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_info"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingBottom="48dp"/>
|
||||
android:paddingBottom="48dp"
|
||||
android:paddingEnd="8dp" />
|
||||
</ScrollView>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
android:id="@+id/container_transaction"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#F0F0F0"
|
||||
android:background="#60F0F0F0"
|
||||
android:elevation="1dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
|
@ -42,7 +42,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp"
|
||||
android:paddingEnd="16dp"
|
||||
android:maxLines="1"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
app:layout_constraintEnd_toStartOf="@id/text_transaction_amount"
|
||||
app:layout_constraintStart_toStartOf="@id/text_transaction_timestamp"
|
||||
|
|
|
@ -3,4 +3,7 @@
|
|||
<color name="colorPrimary">#6200EE</color>
|
||||
<color name="colorPrimaryDark">#3700B3</color>
|
||||
<color name="colorAccent">#03DAC5</color>
|
||||
|
||||
<color name="tx_outbound">#F06292</color>
|
||||
<color name="tx_inbound">#81C784</color>
|
||||
</resources>
|
||||
|
|
|
@ -21,4 +21,7 @@
|
|||
<string name="home_second">Home Second</string>
|
||||
<string name="next">Next</string>
|
||||
<string name="previous">Previous</string>
|
||||
<string name="load">Load</string>
|
||||
<string name="apply">Apply</string>
|
||||
<string name="loading">⌛ Loading</string>
|
||||
</resources>
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
package cash.z.ecc.android.sdk.demoapp
|
||||
|
||||
import cash.z.ecc.android.sdk.Initializer
|
||||
import cash.z.ecc.android.sdk.demoapp.util.SimpleMnemonics
|
||||
import cash.z.ecc.android.sdk.tool.WalletBirthdayTool
|
||||
|
||||
data class DemoConfig(
|
||||
val alias: String = "SdkDemo",
|
||||
val host: String = "lightwalletd.electriccoin.co",
|
||||
val port: Int = 9067,
|
||||
val birthdayHeight: Int = 835_000,
|
||||
val sendAmount: Double = 0.0018,
|
||||
val birthdayHeight: Int = 968000,
|
||||
val sendAmount: Double = 0.000018,
|
||||
|
||||
// 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",
|
||||
|
@ -16,6 +17,6 @@ data class DemoConfig(
|
|||
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)
|
||||
fun newWalletBirthday() = WalletBirthdayTool.loadNearest(App.instance)
|
||||
fun loadBirthday(height: Int = birthdayHeight) = WalletBirthdayTool.loadNearest(App.instance, height)
|
||||
}
|
||||
|
|
|
@ -3,18 +3,18 @@
|
|||
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.3.72'
|
||||
ext.kotlin_version = '1.4.0'
|
||||
ext.sdk_version = '1.0.0-alpha03'
|
||||
repositories {
|
||||
google ()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.0.0'
|
||||
classpath 'com.android.tools.build:gradle:4.0.1'
|
||||
classpath"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.2.2'
|
||||
classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.3.0'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue