Implement send flow.

Added 4 screens and the related logic.
This commit is contained in:
Kevin Gorham 2019-12-23 14:16:00 -05:00
parent 29c024c563
commit fa4415ae99
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
21 changed files with 961 additions and 98 deletions

View File

@ -5,7 +5,7 @@ import cash.z.ecc.android.ui.MainActivityModule
import cash.z.ecc.android.ui.detail.WalletDetailFragmentModule
import cash.z.ecc.android.ui.home.HomeFragmentModule
import cash.z.ecc.android.ui.receive.ReceiveFragmentModule
import cash.z.ecc.android.ui.send.SendFragmentModule
import cash.z.ecc.android.ui.send.*
import cash.z.ecc.android.ui.setup.BackupFragmentModule
import cash.z.ecc.android.ui.setup.LandingFragmentModule
import dagger.BindsInstance
@ -27,7 +27,10 @@ import javax.inject.Singleton
// Fragments
HomeFragmentModule::class,
ReceiveFragmentModule::class,
SendFragmentModule::class,
SendAddressFragmentModule::class,
SendMemoFragmentModule::class,
SendConfirmFragmentModule::class,
SendFinalFragmentModule::class,
WalletDetailFragmentModule::class,
LandingFragmentModule::class,
BackupFragmentModule::class

View File

@ -3,6 +3,8 @@ package cash.z.ecc.android.di
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import cash.z.ecc.android.di.annotation.ViewModelKey
import cash.z.ecc.android.ui.home.HomeViewModel
import cash.z.ecc.android.ui.send.SendViewModel
import cash.z.ecc.android.ui.setup.WalletSetupViewModel
import dagger.Binds
import dagger.Module
@ -21,6 +23,16 @@ abstract class ViewModelModule {
@IntoMap
@ViewModelKey(WalletSetupViewModel::class)
abstract fun bindWalletSetupViewModel(implementation: WalletSetupViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(HomeViewModel::class)
abstract fun bindHomeViewModel(implementation: HomeViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(SendViewModel::class)
abstract fun bindSendViewModel(implementation: SendViewModel): ViewModel
}
@Singleton

View File

@ -14,6 +14,10 @@ fun View.invisibleIf(isInvisible: Boolean) {
visibility = if (isInvisible) INVISIBLE else VISIBLE
}
fun View.disabledIf(isDisabled: Boolean) {
isEnabled = !isDisabled
}
fun View.onClickNavTo(navResId: Int) {
setOnClickListener {
(context as? MainActivity)?.navController?.navigate(navResId)
@ -32,6 +36,16 @@ fun View.onClickNavUp() {
}
}
fun View.onClickNavBack() {
setOnClickListener {
(context as? MainActivity)?.navController?.popBackStack()
?: throw IllegalStateException(
"Cannot navigate from this activity. " +
"Expected MainActivity but found ${context.javaClass.simpleName}"
)
}
}
fun View.clicks() = channelFlow<View> {
setOnClickListener {
offer(this@clicks)

View File

@ -49,12 +49,21 @@ class MainActivity : DaggerAppCompatActivity() {
@Inject
lateinit var feedbackCoordinator: FeedbackCoordinator
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
val sendViewModel: SendViewModel by viewModels { viewModelFactory }
lateinit var navController: NavController
private val mediaPlayer: MediaPlayer = MediaPlayer()
private var snackbar: Snackbar? = null
lateinit var synchronizer: Synchronizer
val clipboard get() = (getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
@ -164,18 +173,15 @@ class MainActivity : DaggerAppCompatActivity() {
}
fun copyAddress(view: View) {
// TODO: get address from synchronizer
val address =
"zs1qduvdyuv83pyygjvc4cfcuc2wj5flnqn730iigf0tjct8k5ccs9y30p96j2gvn9gzyxm6q0vj12c4"
val clipboard: ClipboardManager =
getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setPrimaryClip(
ClipData.newPlainText(
"Z-Address",
address
lifecycleScope.launch {
clipboard.setPrimaryClip(
ClipData.newPlainText(
"Z-Address",
synchronizer.getAddress()
)
)
)
showMessage("Address copied!", "Sweet")
showMessage("Address copied!", "Sweet")
}
}
private fun showMessage(message: String, action: String) {

View File

@ -0,0 +1,113 @@
package cash.z.ecc.android.ui.send
import android.content.ClipboardManager
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.inputmethod.EditorInfo
import cash.z.ecc.android.R
import cash.z.ecc.android.databinding.FragmentSendAddressBinding
import cash.z.ecc.android.di.annotation.FragmentScope
import cash.z.ecc.android.ext.goneIf
import cash.z.ecc.android.ext.onClickNavBack
import cash.z.ecc.android.ui.base.BaseFragment
import cash.z.wallet.sdk.ext.convertZatoshiToZecString
import cash.z.wallet.sdk.ext.twig
import dagger.Module
import dagger.android.ContributesAndroidInjector
class SendAddressFragment : BaseFragment<FragmentSendAddressBinding>(),
ClipboardManager.OnPrimaryClipChangedListener {
override fun inflate(inflater: LayoutInflater): FragmentSendAddressBinding =
FragmentSendAddressBinding.inflate(inflater)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonNext.setOnClickListener {
onAddAddress()
}
binding.backButtonHitArea.onClickNavBack()
binding.textBannerAction.setOnClickListener {
onPaste()
}
binding.textBannerMessage.setOnClickListener {
onPaste()
}
binding.textAmount.text = "Sending ${mainActivity?.sendViewModel?.zatoshiAmount.convertZatoshiToZecString(8)} ZEC"
binding.inputZcashAddress.setOnEditorActionListener { v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
onAddAddress()
true
} else {
false
}
}
}
private fun onAddAddress() {
mainActivity?.sendViewModel?.toAddress = binding.inputZcashAddress.text.toString()
mainActivity?.navController?.navigate(R.id.action_nav_send_address_to_send_memo)
}
override fun onAttach(context: Context) {
super.onAttach(context)
mainActivity?.clipboard?.addPrimaryClipChangedListener(this)
}
override fun onDetach() {
super.onDetach()
mainActivity?.clipboard?.removePrimaryClipChangedListener(this)
}
override fun onResume() {
super.onResume()
updateClipboardBanner()
}
override fun onPrimaryClipChanged() {
twig("clipboard changed!")
updateClipboardBanner()
}
private fun updateClipboardBanner() {
binding.groupBanner.goneIf(loadAddressFromClipboard() == null)
}
private fun onPaste() {
mainActivity?.clipboard?.let { clipboard ->
if (clipboard.hasPrimaryClip()) {
binding.inputZcashAddress.setText(clipboard.text())
}
}
}
private fun loadAddressFromClipboard(): String? {
mainActivity?.clipboard?.apply {
if (hasPrimaryClip()) {
text()?.let { text ->
if (text.startsWith("zs") && text.length > 70) {
return@loadAddressFromClipboard text.toString()
}
// treat t-addrs differently in the future
if (text.startsWith("t1") && text.length > 32) {
return@loadAddressFromClipboard text.toString()
}
}
}
}
return null
}
private fun ClipboardManager.text(): CharSequence =
primaryClip!!.getItemAt(0).coerceToText(mainActivity)
}
@Module
abstract class SendAddressFragmentModule {
@FragmentScope
@ContributesAndroidInjector
abstract fun contributeFragment(): SendAddressFragment
}

View File

@ -0,0 +1,53 @@
package cash.z.ecc.android.ui.send
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import androidx.lifecycle.lifecycleScope
import cash.z.ecc.android.R
import cash.z.ecc.android.databinding.FragmentSendConfirmBinding
import cash.z.ecc.android.di.annotation.FragmentScope
import cash.z.ecc.android.ext.goneIf
import cash.z.ecc.android.ext.onClickNavBack
import cash.z.ecc.android.ui.MainActivity
import cash.z.ecc.android.ui.base.BaseFragment
import cash.z.wallet.sdk.ext.abbreviatedAddress
import cash.z.wallet.sdk.ext.convertZatoshiToZecString
import dagger.Module
import dagger.android.ContributesAndroidInjector
import kotlinx.coroutines.launch
class SendConfirmFragment : BaseFragment<FragmentSendConfirmBinding>() {
override fun inflate(inflater: LayoutInflater): FragmentSendConfirmBinding =
FragmentSendConfirmBinding.inflate(inflater)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mainActivity?.apply {
binding.buttonNext.setOnClickListener {
onSend()
}
binding.backButtonHitArea.onClickNavBack()
mainActivity?.lifecycleScope?.launch {
binding.textConfirmation.text =
"Send ${sendViewModel?.zatoshiAmount.convertZatoshiToZecString(8)} ZEC to ${sendViewModel?.toAddress.abbreviatedAddress()}?"
}
sendViewModel.memo.trim().isNotEmpty().let { hasMemo ->
binding.radioIncludeAddress.isChecked = hasMemo
binding.radioIncludeAddress.goneIf(!hasMemo)
}
}
}
private fun onSend() {
mainActivity?.navController?.navigate(R.id.action_nav_send_confirm_to_send_final)
}
}
@Module
abstract class SendConfirmFragmentModule {
@FragmentScope
@ContributesAndroidInjector
abstract fun contributeFragment(): SendConfirmFragment
}

View File

@ -0,0 +1,103 @@
package cash.z.ecc.android.ui.send
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import androidx.lifecycle.lifecycleScope
import cash.z.ecc.android.R
import cash.z.ecc.android.databinding.FragmentSendFinalBinding
import cash.z.ecc.android.di.annotation.FragmentScope
import cash.z.ecc.android.ext.goneIf
import cash.z.ecc.android.ui.base.BaseFragment
import cash.z.wallet.sdk.entity.*
import cash.z.wallet.sdk.ext.abbreviatedAddress
import cash.z.wallet.sdk.ext.convertZatoshiToZecString
import cash.z.wallet.sdk.ext.twig
import dagger.Module
import dagger.android.ContributesAndroidInjector
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlin.math.min
import kotlin.random.Random
class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
override fun inflate(inflater: LayoutInflater): FragmentSendFinalBinding =
FragmentSendFinalBinding.inflate(inflater)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonNext.setOnClickListener {
onExit()
}
binding.backButtonHitArea.setOnClickListener {
onExit()
}
binding.textConfirmation.text =
"Sending ${mainActivity?.sendViewModel?.zatoshiAmount.convertZatoshiToZecString(8)} ZEC to ${mainActivity?.sendViewModel?.toAddress?.abbreviatedAddress()}"
mainActivity?.sendViewModel?.memo?.trim()?.isNotEmpty()?.let { hasMemo ->
binding.radioIncludeAddress.isChecked = hasMemo
binding.radioIncludeAddress.goneIf(!hasMemo)
}
}
override fun onAttach(context: Context) {
super.onAttach(context)
mainActivity?.apply {
sendViewModel.send(synchronizer).onEach {
onPendingTxUpdated(it)
}.launchIn(mainActivity?.lifecycleScope!!)
}
}
override fun onResume() {
super.onResume()
flow {
val max = binding.progressHorizontal.max - 1
var progress = 0
while (progress < max) {
emit(progress)
delay(Random.nextLong(1000))
progress++
}
}.onEach {
binding.progressHorizontal.progress = it
}.launchIn(resumedScope)
}
private fun onPendingTxUpdated(pendingTransaction: PendingTransaction?) {
val id = pendingTransaction?.id ?: -1
var isSending = true
val message = when {
pendingTransaction == null -> "Transaction not found"
pendingTransaction.isMined() -> "Transaction Mined (id: $id)!\n\nSEND COMPLETE".also { isSending = false }
pendingTransaction.isSubmitSuccess() -> "Successfully submitted transaction!\nAwaiting confirmation . . ."
pendingTransaction.isFailedEncoding() -> "ERROR: failed to encode transaction! (id: $id)".also { isSending = false }
pendingTransaction.isFailedSubmit() -> "ERROR: failed to submit transaction! (id: $id)".also { isSending = false }
pendingTransaction.isCreated() -> "Transaction creation complete! (id: $id)"
pendingTransaction.isCreating() -> "Creating transaction . . ."
else -> "Transaction updated!".also { twig("Unhandled TX state: $pendingTransaction") }
}
twig("Pending TX Updated: $message")
binding.textStatus.apply {
text = "$text\n$message"
}
binding.backButton.goneIf(!binding.textStatus.text.toString().contains("Awaiting"))
binding.buttonNext.goneIf(isSending)
binding.progressHorizontal.goneIf(!isSending)
}
private fun onExit() {
mainActivity?.navController?.popBackStack(R.id.send_navigation, true)
}
}
@Module
abstract class SendFinalFragmentModule {
@FragmentScope
@ContributesAndroidInjector
abstract fun contributeFragment(): SendFinalFragment
}

View File

@ -1,29 +0,0 @@
package cash.z.ecc.android.ui.send
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import cash.z.ecc.android.databinding.FragmentSendBinding
import cash.z.ecc.android.di.annotation.FragmentScope
import cash.z.ecc.android.ext.onClickNavUp
import cash.z.ecc.android.ui.base.BaseFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
class SendFragment : BaseFragment<FragmentSendBinding>() {
override fun inflate(inflater: LayoutInflater): FragmentSendBinding =
FragmentSendBinding.inflate(inflater)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.backButtonHitArea.onClickNavUp()
}
}
@Module
abstract class SendFragmentModule {
@FragmentScope
@ContributesAndroidInjector
abstract fun contributeFragment(): SendFragment
}

View File

@ -0,0 +1,61 @@
package cash.z.ecc.android.ui.send
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.inputmethod.EditorInfo
import cash.z.ecc.android.R
import cash.z.ecc.android.databinding.FragmentSendMemoBinding
import cash.z.ecc.android.di.annotation.FragmentScope
import cash.z.ecc.android.ext.onClickNavBack
import cash.z.ecc.android.ext.onClickNavUp
import cash.z.ecc.android.ui.base.BaseFragment
import dagger.Module
import dagger.android.ContributesAndroidInjector
class SendMemoFragment : BaseFragment<FragmentSendMemoBinding>() {
override fun inflate(inflater: LayoutInflater): FragmentSendMemoBinding =
FragmentSendMemoBinding.inflate(inflater)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonNext.setOnClickListener {
onAddMemo()
}
binding.buttonSkip.setOnClickListener {
binding.inputMemo.setText("")
mainActivity?.sendViewModel?.memo = ""
mainActivity?.navController?.navigate(R.id.action_nav_send_memo_to_send_confirm)
}
binding.backButtonHitArea.onClickNavBack()
binding.radioIncludeAddress.setOnClickListener {
if (binding.radioIncludeAddress.isActivated) {
binding.radioIncludeAddress.isChecked = false
} else {
binding.radioIncludeAddress.isActivated = true
}
}
binding.inputMemo.setOnEditorActionListener { v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
onAddMemo()
true
} else {
false
}
}
binding.radioIncludeAddress.requestFocus()
}
private fun onAddMemo() {
mainActivity?.sendViewModel?.memo = binding.inputMemo.text.toString()
mainActivity?.navController?.navigate(R.id.action_nav_send_memo_to_send_confirm)
}
}
@Module
abstract class SendMemoFragmentModule {
@FragmentScope
@ContributesAndroidInjector
abstract fun contributeFragment(): SendMemoFragment
}

View File

@ -0,0 +1,32 @@
package cash.z.ecc.android.ui.send
import androidx.lifecycle.ViewModel
import cash.z.ecc.android.lockbox.LockBox
import cash.z.ecc.android.ui.setup.WalletSetupViewModel
import cash.z.wallet.sdk.SdkSynchronizer
import cash.z.wallet.sdk.Synchronizer
import cash.z.wallet.sdk.entity.PendingTransaction
import cash.z.wallet.sdk.ext.twig
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onEach
import javax.inject.Inject
class SendViewModel @Inject constructor(var lockBox: LockBox) : ViewModel() {
fun send(synchronizer: Synchronizer): Flow<PendingTransaction> {
val keys = (synchronizer as SdkSynchronizer).rustBackend!!.deriveSpendingKeys(
lockBox.getBytes(WalletSetupViewModel.LockBoxKey.SEED)!!
)
return synchronizer.sendToAddress(
keys[0],
zatoshiAmount,
toAddress,
memo
).onEach {
twig(it.toString())
}
}
var toAddress: String = ""
var memo: String = ""
var zatoshiAmount: Long = -1L
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:endColor="@color/colorPrimaryDark"
android:startColor="@color/colorPrimary"
android:type="radial"
android:centerY="0.36"
android:centerX="0.50"
android:gradientRadius="640dp"/>
</shape>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

View File

@ -1,45 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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:background="@drawable/background_home"
xmlns:tools="http://schemas.android.com/tools">
<!-- Back Button -->
<ImageView
android:id="@+id/back_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tint="@color/text_light"
app:srcCompat="@drawable/ic_arrow_back_black_24dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0.065"
app:layout_constraintHorizontal_bias="0.05"/>
<View
android:id="@+id/back_button_hit_area"
android:layout_width="56dp"
android:layout_height="56dp"
android:clickable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.01"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.045" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/text_light"
android:text="SCAN"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,154 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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:background="@drawable/background_home"
xmlns:tools="http://schemas.android.com/tools">
<!-- Back Button -->
<ImageView
android:id="@+id/back_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tint="@color/text_light"
app:srcCompat="@drawable/ic_arrow_back_black_24dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0.065"
app:layout_constraintHorizontal_bias="0.05"/>
<View
android:id="@+id/back_button_hit_area"
android:layout_width="56dp"
android:layout_height="56dp"
android:clickable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.01"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.045" />
<TextView
android:id="@+id/text_amount"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="@style/TextAppearance.MaterialComponents.Headline6"
tools:text="Sending 12.34121212 ZEC"
android:textColor="@color/text_light"
android:autoSizeTextType="uniform"
android:maxLines="1"
app:layout_constraintStart_toEndOf="@id/back_button_hit_area"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/back_button"
app:layout_constraintBottom_toBottomOf="@id/back_button" />
<!-- Input: Address -->
<EditText
android:id="@+id/input_zcash_address"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:hint="@string/send_hint_input_zcash_address"
android:imeOptions="actionDone"
android:inputType="textNoSuggestions"
android:paddingRight="76dp"
android:singleLine="true"
android:paddingTop="0dp"
android:textColor="@color/text_light"
app:backgroundTint="@color/colorPrimary"
android:textColorHint="@color/text_light_dimmed"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintWidth_percent="0.84"
app:layout_constraintVertical_bias="0.2"/>
<TextView
android:id="@+id/text_address_error"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:fontFamily="@font/inconsolata"
android:includeFontPadding="false"
android:textColor="@android:color/holo_red_light"
android:textSize="12dp"
android:visibility="gone"
app:layout_constraintStart_toStartOf="@id/input_zcash_address"
app:layout_constraintTop_toBottomOf="@id/input_zcash_address"
tools:text="invalid address" />
<!-- Scan QR code -->
<ImageView
android:id="@+id/image_scan_qr"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="6dp"
android:paddingTop="10dp"
android:paddingEnd="1dp"
android:paddingBottom="24dp"
android:tint="@color/zcashWhite"
app:layout_constraintBottom_toBottomOf="@id/input_zcash_address"
app:layout_constraintEnd_toEndOf="@id/input_zcash_address"
app:layout_constraintTop_toTopOf="@id/input_zcash_address"
app:srcCompat="@drawable/ic_qrcode_24dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Next"
android:textColor="@color/text_dark"
app:layout_constraintEnd_toEndOf="@+id/input_zcash_address"
app:layout_constraintTop_toBottomOf="@+id/input_zcash_address" />
<!-- -->
<!-- Banner -->
<!-- -->
<TextView
android:id="@+id/text_banner_message"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="@drawable/background_banner"
android:elevation="6dp"
android:paddingBottom="8dp"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:text="Address on clipboard!"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/text_light"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/input_zcash_address"
app:layout_constraintStart_toStartOf="@+id/input_zcash_address"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.45"/>
<TextView
android:id="@+id/text_banner_action"
android:elevation="6dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="Paste"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/colorPrimary"
app:layout_constraintBaseline_toBaselineOf="@id/text_banner_message"
app:layout_constraintEnd_toEndOf="@id/text_banner_message" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_banner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="text_banner_message, text_banner_action"
android:visibility="visible"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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"
xmlns:tools="http://schemas.android.com/tools"
android:background="@drawable/background_home">
<!-- Back Button -->
<ImageView
android:id="@+id/back_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tint="@color/text_light"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.05"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.065"
app:srcCompat="@drawable/ic_arrow_back_black_24dp" />
<View
android:id="@+id/back_button_hit_area"
android:layout_width="56dp"
android:layout_height="56dp"
android:clickable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.01"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.045" />
<TextView
android:id="@+id/text_confirmation"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="@style/TextAppearance.MaterialComponents.Headline4"
android:gravity="center"
tools:text="Send 12.345 ZEC to\nzs1g7sqw...mvyzgm?"
android:textColor="@color/text_light"
android:maxLines="3"
android:autoSizeTextType="uniform"
android:paddingStart="16dp"
android:paddingEnd="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.21"/>
<RadioButton
android:id="@+id/radio_include_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Includes memo"
android:enabled="false"
style="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/colorAccent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_confirmation" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="+.0001 Network Fee"
style="@style/TextAppearance.MaterialComponents.Body2"
android:textColor="@color/colorAccent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintVertical_bias="0.84"
/>
<com.google.android.material.button.MaterialButton
android:id="@+id/button_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="Tap\nto send ZEC"
android:textColor="@color/text_dark"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.48" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,121 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
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"
xmlns:tools="http://schemas.android.com/tools"
android:background="@drawable/background_send_final"
android:animateLayoutChanges="true"
>
<View
android:id="@+id/guide_keys"
android:layout_width="0dp"
android:layout_height="0dp"
android:alpha="0.3"
android:background="@android:color/transparent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.38196601125"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.58"
app:layout_constraintWidth_percent="0.7475728155" />
<!-- Back Button -->
<ImageView
android:id="@+id/back_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.05"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.065"
app:srcCompat="@drawable/ic_close_black_24dp" />
<View
android:id="@+id/back_button_hit_area"
android:layout_width="56dp"
android:layout_height="56dp"
android:clickable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.01"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.045" />
<TextView
android:id="@+id/text_confirmation"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="@style/TextAppearance.MaterialComponents.Headline4"
android:gravity="center"
tools:text="Send 12.345 ZEC to\nzs1g7sqw...mvyzgm?"
android:textColor="@color/text_dark"
android:maxLines="3"
android:autoSizeTextType="uniform"
android:paddingStart="16dp"
android:paddingEnd="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.21"/>
<TextView
android:id="@+id/text_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/text_dark"
tools:text="Creating transaction..."
android:textSize="16dp"
app:layout_constraintTop_toBottomOf="@id/radio_include_address"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintVertical_bias="0.1"/>
<ProgressBar
android:id="@+id/progress_horizontal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
app:layout_constraintStart_toStartOf="@id/text_status"
app:layout_constraintEnd_toEndOf="@id/text_status"
app:layout_constraintTop_toBottomOf="@id/text_status"
android:indeterminate="false"
style="?android:attr/progressBarStyleHorizontal"
android:max="150"
android:foregroundTint="@color/zcashBlack_87" />
<RadioButton
android:id="@+id/radio_include_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/TextAppearance.MaterialComponents.Body1"
android:text="Includes memo"
android:enabled="false"
android:textColor="@color/text_dark_dimmed"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_confirmation" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_next"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
style="@style/Zcash.Button.OutlinedButton"
app:strokeColor="@color/text_dark"
android:padding="12dp"
android:text="Finished"
android:textColor="@color/text_dark"
app:layout_constraintEnd_toEndOf="@id/guide_keys"
app:layout_constraintStart_toStartOf="@id/guide_keys"
app:layout_constraintTop_toBottomOf="@id/guide_keys" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="utf-8"?>
<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"
android:background="@drawable/background_home">
<!-- Back Button -->
<ImageView
android:id="@+id/back_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:tint="@color/text_light"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.05"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.065"
app:srcCompat="@drawable/ic_arrow_back_black_24dp" />
<View
android:id="@+id/back_button_hit_area"
android:layout_width="56dp"
android:layout_height="56dp"
android:clickable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.01"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.045" />
<EditText
android:id="@+id/input_memo"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/background_banner"
android:elevation="6dp"
android:gravity="top"
android:imeActionLabel="add memo"
android:inputType="textImeMultiLine"
android:imeOptions="actionDone"
android:hint="Add a memo here"
android:paddingBottom="8dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="8dp"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/text_light"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.24"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.185"
app:layout_constraintWidth_percent="0.8"
tools:text="This is my memo" />
<RadioButton
android:id="@+id/radio_include_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Include your sending address in memo"
app:layout_constraintStart_toStartOf="@+id/input_memo"
app:layout_constraintTop_toBottomOf="@+id/input_memo" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:gravity="center"
android:text="Your transaction is shielded and your address is unavailable to recipient"
app:layout_constraintEnd_toEndOf="@id/input_memo"
app:layout_constraintStart_toStartOf="@id/input_memo"
app:layout_constraintTop_toBottomOf="@id/radio_include_address" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_next"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:padding="12dp"
android:text="Add Memo"
android:textColor="@color/text_dark"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.8"
app:layout_constraintWidth_percent="0.68" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_skip"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="@style/Zcash.Button.OutlinedButton"
android:padding="12dp"
android:text="Send without memo"
android:textColor="@color/text_light"
app:layout_constraintEnd_toEndOf="@id/button_next"
app:layout_constraintStart_toStartOf="@id/button_next"
app:layout_constraintTop_toBottomOf="@id/button_next" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -13,22 +13,17 @@
<action
android:id="@+id/action_nav_home_to_nav_receive"
app:destination="@id/nav_receive" />
<action
android:id="@+id/action_nav_home_to_nav_send"
app:destination="@id/nav_send" />
<action
android:id="@+id/action_nav_home_to_nav_detail"
app:destination="@id/nav_detail" />
<action
android:id="@+id/action_nav_home_to_create_wallet"
app:destination="@id/wallet_setup_navigation" />
<action
android:id="@+id/action_nav_home_to_send"
app:destination="@id/send_navigation" />
</fragment>
<fragment
android:id="@+id/nav_send"
android:name="cash.z.ecc.android.ui.send.SendFragment"
tools:layout="@layout/fragment_send" />
<fragment
android:id="@+id/nav_receive"
android:name="cash.z.ecc.android.ui.receive.ReceiveFragment"
@ -44,5 +39,6 @@
</fragment>
<include app:graph="@navigation/wallet_setup_navigation" />
<include app:graph="@navigation/send_navigation" />
</navigation>

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation
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:id="@+id/send_navigation"
app:startDestination="@+id/nav_send_address">
<fragment
android:id="@+id/nav_send_address"
android:name="cash.z.ecc.android.ui.send.SendAddressFragment"
tools:layout="@layout/fragment_send_address" >
<action
android:id="@+id/action_nav_send_address_to_send_memo"
app:destination="@id/nav_send_memo"/>
</fragment>
<fragment
android:id="@+id/nav_send_memo"
android:name="cash.z.ecc.android.ui.send.SendMemoFragment"
tools:layout="@layout/fragment_send_memo" >
<action
android:id="@+id/action_nav_send_memo_to_send_confirm"
app:destination="@id/nav_send_confirm"/>
</fragment>
<fragment
android:id="@+id/nav_send_confirm"
android:name="cash.z.ecc.android.ui.send.SendConfirmFragment"
tools:layout="@layout/fragment_send_confirm" >
<action
android:id="@+id/action_nav_send_confirm_to_send_final"
app:destination="@id/nav_send_final"/>
</fragment>
<fragment
android:id="@+id/nav_send_final"
android:name="cash.z.ecc.android.ui.send.SendFinalFragment"
tools:layout="@layout/fragment_send_final" >
</fragment>
</navigation>

View File

@ -4,4 +4,7 @@
<string name="mixpanel_project">a178e1ef062133fc121079cb12fa43c7</string>
<!-- Send Flow -->
<string name="send_hint_input_zcash_address">Enter a shielded Zcash address</string>
</resources>

View File

@ -26,11 +26,15 @@
<item name="android:background">@drawable/selector_pressed_ripple_circle</item>
</style>
<style name="Zcash.Button.White" parent="Widget.MaterialComponents.Button">
<item name="backgroundTint">@android:color/white</item>
<style name="Zcash.Button" parent="Widget.MaterialComponents.Button">
<item name="backgroundTint">@color/colorPrimary</item>
<item name="android:textColor">@color/text_dark</item>
</style>
<style name="Zcash.Button.White">
<item name="backgroundTint">@android:color/white</item>
</style>
<style name="Zcash.Button.OutlinedButton" parent="Widget.MaterialComponents.Button.OutlinedButton">
<item name="strokeColor">@color/zcashWhite</item>
</style>
@ -65,7 +69,7 @@
<style name="Zcash.ShapeAppearance.SmallComponent" parent="ShapeAppearance.MaterialComponents.SmallComponent">
<item name="cornerFamily">cut</item>
<item name="cornerSize">8dp</item>
<item name="cornerSize">10dp</item>
</style>
<style name="Zcash.ShapeAppearance.MediumComponent" parent="ShapeAppearance.MaterialComponents.MediumComponent">
<item name="cornerFamily">cut</item>