Merge pull request #59 from zcash/feature/qr-code-scan

Feature/qr code scan
This commit is contained in:
Kevin Gorham 2020-01-08 03:50:27 -05:00 committed by GitHub
commit 7f026f033e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 1160 additions and 397 deletions

View File

@ -4,6 +4,7 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: 'com.google.gms.google-services'
//apply plugin: 'com.github.ben-manes.versions'
archivesBaseName = 'zcash-android-wallet'
@ -135,6 +136,14 @@ dependencies {
implementation 'com.mixpanel.android:mixpanel-android:5.6.3'
// QR Scanning
implementation 'com.google.firebase:firebase-ml-vision:24.0.1'
implementation 'androidx.camera:camera-core:1.0.0-alpha08'
implementation 'androidx.camera:camera-camera2:1.0.0-alpha08'
implementation "androidx.camera:camera-view:1.0.0-alpha05"
implementation "androidx.camera:camera-extensions:1.0.0-alpha05"
implementation "androidx.camera:camera-lifecycle:1.0.0-alpha02"
// Tests
implementation 'androidx.legacy:legacy-support-v4:1.0.0'

View File

@ -82,7 +82,7 @@ class IntegrationTest {
fun testAddress() {
val seed = mnemonics.toSeed(phrase.toCharArray())
val initializer = Initializer(appContext).apply {
new(seed, overwrite = true)
new(seed, Initializer.DefaultBirthdayStore(appContext).newWalletBirthday, overwrite = true)
}
assertEquals(
"Generated incorrect z-address!",

View File

@ -3,6 +3,7 @@
package="cash.z.ecc.android">
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.CAMERA" />
<application
android:name="cash.z.ecc.android.ZcashWalletApp"
@ -19,6 +20,9 @@
</intent-filter>
</activity>
<!-- Firebase options -->
<meta-data android:name="com.google.firebase.ml.vision.DEPENDENCIES" android:value="barcode" />
<!-- Mixpanel options -->
<meta-data android:name="com.mixpanel.android.MPConfig.AutoShowMixpanelUpdates" android:value="false" />
<meta-data android:name="com.mixpanel.android.MPConfig.EnableDebugLogging" android:value="false" />

View File

@ -3,6 +3,8 @@ package cash.z.ecc.android
import android.app.Application
import android.content.Context
import android.os.Build
import androidx.camera.camera2.Camera2Config
import androidx.camera.core.CameraXConfig
import cash.z.ecc.android.di.component.AppComponent
import cash.z.ecc.android.di.component.DaggerAppComponent
import cash.z.wallet.sdk.ext.TroubleshootingTwig
@ -10,7 +12,7 @@ import cash.z.wallet.sdk.ext.Twig
import cash.z.wallet.sdk.ext.twig
class ZcashWalletApp : Application() {
class ZcashWalletApp : Application(), CameraXConfig.Provider {
var creationTime: Long = 0
private set
@ -33,6 +35,10 @@ class ZcashWalletApp : Application() {
// MultiDex.install(this)
}
override fun getCameraXConfig(): CameraXConfig {
return Camera2Config.defaultConfig()
}
companion object {
lateinit var instance: ZcashWalletApp
lateinit var component: AppComponent

View File

@ -8,8 +8,8 @@ import dagger.Reusable
@Module
class InitializerModule {
private val host = "lightd-main.zecwallet.co"
private val port = 443
private val host = "lightwalletd.z.cash"
private val port = 9067
@Provides
@Reusable

View File

@ -9,6 +9,7 @@ import cash.z.ecc.android.di.viewmodel.ViewModelFactory
import cash.z.ecc.android.ui.detail.WalletDetailViewModel
import cash.z.ecc.android.ui.home.HomeViewModel
import cash.z.ecc.android.ui.receive.ReceiveViewModel
import cash.z.ecc.android.ui.scan.ScanViewModel
import cash.z.ecc.android.ui.send.SendViewModel
import dagger.Binds
import dagger.Module
@ -44,6 +45,11 @@ abstract class ViewModelsSynchronizerModule {
@ViewModelKey(ReceiveViewModel::class)
abstract fun bindReceiveViewModel(implementation: ReceiveViewModel): ViewModel
@SynchronizerScope
@Binds
@IntoMap
@ViewModelKey(ScanViewModel::class)
abstract fun bindScanViewModel(implementation: ScanViewModel): ViewModel
/**
* Factory for view models that are not created until the Synchronizer exists. Only VMs that

View File

@ -0,0 +1,29 @@
package cash.z.ecc.android.ext
import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
import android.widget.EditText
import android.widget.TextView
import cash.z.wallet.sdk.ext.convertZecToZatoshi
import cash.z.wallet.sdk.ext.safelyConvertToBigDecimal
import cash.z.wallet.sdk.ext.twig
fun EditText.onEditorActionDone(block: (EditText) -> Unit) {
this.setOnEditorActionListener { _, actionId, _ ->
if (actionId == IME_ACTION_DONE) {
block(this)
true
} else {
false
}
}
}
fun TextView.convertZecToZatoshi(): Long? {
return try {
text.toString().safelyConvertToBigDecimal()?.convertZecToZatoshi() ?: null
} catch (t: Throwable) {
twig("Failed to convert text to Zatoshi: $text")
null
}
}

View File

@ -90,7 +90,7 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
hitAreaReceive.onClickNavTo(R.id.action_nav_home_to_nav_receive)
iconDetail.onClickNavTo(R.id.action_nav_home_to_nav_detail)
textDetail.onClickNavTo(R.id.action_nav_home_to_nav_detail)
// hitAreaScan.onClickNavTo(R.id.action_nav_home_to_nav_send)
hitAreaScan.onClickNavTo(R.id.action_nav_home_to_nav_scan)
textBannerAction.setOnClickListener {
onBannerAction(BannerAction.from((it as? TextView)?.text?.toString()))

View File

@ -2,47 +2,46 @@ package cash.z.ecc.android.ui.receive
import android.content.Context
import android.os.Bundle
import android.text.SpannableString
import android.text.Spanned
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.TextView
import android.widget.Toast
import cash.z.android.qrecycler.QRecycler
import cash.z.ecc.android.databinding.FragmentReceiveBinding
import cash.z.ecc.android.R
import cash.z.ecc.android.databinding.FragmentReceiveNewBinding
import cash.z.ecc.android.di.viewmodel.viewModel
import cash.z.ecc.android.ext.onClickNavUp
import cash.z.ecc.android.ext.onClickNavBack
import cash.z.ecc.android.ext.onClickNavTo
import cash.z.ecc.android.ui.base.BaseFragment
import cash.z.ecc.android.ui.util.AddressPartNumberSpan
import cash.z.wallet.sdk.ext.abbreviatedAddress
import cash.z.wallet.sdk.ext.twig
import kotlinx.android.synthetic.main.fragment_receive.*
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
class ReceiveFragment : BaseFragment<FragmentReceiveBinding>() {
class ReceiveFragment : BaseFragment<FragmentReceiveNewBinding>() {
private val viewModel: ReceiveViewModel by viewModel()
lateinit var qrecycler: QRecycler
lateinit var addressParts: Array<TextView>
// lateinit var addressParts: Array<TextView>
override fun inflate(inflater: LayoutInflater): FragmentReceiveBinding =
FragmentReceiveBinding.inflate(inflater)
override fun inflate(inflater: LayoutInflater): FragmentReceiveNewBinding =
FragmentReceiveNewBinding.inflate(inflater)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
addressParts = arrayOf(
text_address_part_1,
text_address_part_2,
text_address_part_3,
text_address_part_4,
text_address_part_5,
text_address_part_6,
text_address_part_7,
text_address_part_8
)
binding.backButtonHitArea.onClickNavUp()
// addressParts = arrayOf(
// text_address_part_1,
// text_address_part_2,
// text_address_part_3,
// text_address_part_4,
// text_address_part_5,
// text_address_part_6,
// text_address_part_7,
// text_address_part_8
// )
binding.buttonScan.onClickNavTo(R.id.action_nav_receive_to_nav_scan)
binding.backButtonHitArea.onClickNavBack()
}
override fun onAttach(context: Context) {
@ -62,11 +61,13 @@ class ReceiveFragment : BaseFragment<FragmentReceiveBinding>() {
qrecycler.load(address)
.withQuietZoneSize(3)
.withCorrectionLevel(QRecycler.CorrectionLevel.MEDIUM)
.into(receive_qr_code)
.into(binding.receiveQrCode)
address.distribute(8) { i, part ->
setAddressPart(i, part)
}
binding.receiveAddress.text = address.abbreviatedAddress(12, 12)
// address.distribute(8) { i, part ->
// setAddressPart(i, part)
// }
}
private fun <T> String.distribute(chunks: Int, block: (Int, String) -> T) {
@ -83,13 +84,13 @@ class ReceiveFragment : BaseFragment<FragmentReceiveBinding>() {
}
}
private fun setAddressPart(index: Int, addressPart: String) {
Log.e("TWIG", "setting address for part $index) $addressPart")
val thinSpace = "\u2005" // 0.25 em space
val textSpan = SpannableString("${index + 1}$thinSpace$addressPart")
textSpan.setSpan(AddressPartNumberSpan(), 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
addressParts[index].text = textSpan
}
// private fun setAddressPart(index: Int, addressPart: String) {
// Log.e("TWIG", "setting address for part $index) $addressPart")
// val thinSpace = "\u2005" // 0.25 em space
// val textSpan = SpannableString("${index + 1}$thinSpace$addressPart")
//
// textSpan.setSpan(AddressPartNumberSpan(), 0, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
//
// addressParts[index].text = textSpan
// }
}

View File

@ -0,0 +1,58 @@
package cash.z.ecc.android.ui.scan
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import cash.z.wallet.sdk.ext.twig
import com.google.android.gms.tasks.Task
import com.google.firebase.ml.vision.FirebaseVision
import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcode
import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcodeDetector
import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcodeDetectorOptions
import com.google.firebase.ml.vision.common.FirebaseVisionImage
import com.google.firebase.ml.vision.common.FirebaseVisionImageMetadata
class QrAnalyzer(val scanCallback: (qrContent: String, image: ImageProxy) -> Unit) :
ImageAnalysis.Analyzer {
private val detector: FirebaseVisionBarcodeDetector by lazy {
val options = FirebaseVisionBarcodeDetectorOptions.Builder()
.setBarcodeFormats(FirebaseVisionBarcode.FORMAT_QR_CODE)
.build()
FirebaseVision.getInstance().getVisionBarcodeDetector(options)
}
var pendingTask: Task<out Any>? = null
override fun analyze(image: ImageProxy) {
var rotation = image.imageInfo.rotationDegrees % 360
if (rotation < 0) {
rotation += 360
}
val mediaImage = FirebaseVisionImage.fromMediaImage(
image.image!!, when (rotation) {
0 -> FirebaseVisionImageMetadata.ROTATION_0
90 -> FirebaseVisionImageMetadata.ROTATION_90
180 -> FirebaseVisionImageMetadata.ROTATION_180
270 -> FirebaseVisionImageMetadata.ROTATION_270
else -> {
FirebaseVisionImageMetadata.ROTATION_0
}
}
)
pendingTask = detector.detectInImage(mediaImage).also {
it.addOnSuccessListener { result ->
onImageScan(result, image)
}
it.addOnFailureListener(::onImageScanFailure)
}
}
private fun onImageScan(result: List<FirebaseVisionBarcode>, image: ImageProxy) {
result.firstOrNull()?.rawValue?.let {
scanCallback(it, image)
} ?: image.close()
}
private fun onImageScanFailure(e: Exception) {
twig("Warning: Image scan failed")
}
}

View File

@ -0,0 +1,180 @@
package cash.z.ecc.android.ui.scan
import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.content.ContextCompat
import cash.z.ecc.android.R
import cash.z.ecc.android.databinding.FragmentScanBinding
import cash.z.ecc.android.di.viewmodel.activityViewModel
import cash.z.ecc.android.di.viewmodel.viewModel
import cash.z.ecc.android.ext.onClickNavBack
import cash.z.ecc.android.ext.onClickNavTo
import cash.z.ecc.android.ui.base.BaseFragment
import cash.z.ecc.android.ui.send.SendViewModel
import com.google.common.util.concurrent.ListenableFuture
import kotlinx.coroutines.launch
import java.util.concurrent.Executors
class ScanFragment : BaseFragment<FragmentScanBinding>() {
private val viewModel: ScanViewModel by viewModel()
private val sendViewModel: SendViewModel by activityViewModel()
private lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
override fun inflate(inflater: LayoutInflater): FragmentScanBinding =
FragmentScanBinding.inflate(inflater)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonReceive.onClickNavTo(R.id.action_nav_scan_to_nav_receive)
binding.backButtonHitArea.onClickNavBack()
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
if (!allPermissionsGranted()) getRuntimePermissions()
}
override fun onAttach(context: Context) {
super.onAttach(context)
cameraProviderFuture = ProcessCameraProvider.getInstance(context)
cameraProviderFuture.addListener(Runnable {
bindPreview(cameraProviderFuture.get())
}, ContextCompat.getMainExecutor(context))
}
private fun bindPreview(cameraProvider: ProcessCameraProvider) {
Preview.Builder().setTargetName("Preview").build().let { preview ->
preview.previewSurfaceProvider = binding.preview.previewSurfaceProvider
val cameraSelector = CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build()
val imageAnalysis = ImageAnalysis.Builder()
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor(), QrAnalyzer { q, i ->
onQrScanned(q, i)
})
cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalysis)
}
}
private fun onQrScanned(qrContent: String, image: ImageProxy) {
resumedScope.launch {
if (viewModel.isNotValid(qrContent)) image.close() // continue scanning
else {
sendViewModel.toAddress = qrContent
mainActivity?.navController?.navigate(R.id.action_nav_scan_to_nav_send_address)
}
}
}
// private fun updateOverlay(detectedObjects: DetectedObjects) {
// if (detectedObjects.objects.isEmpty()) {
// return
// }
//
// overlay.setSize(detectedObjects.imageWidth, detectedObjects.imageHeight)
// val list = mutableListOf<BoxData>()
// for (obj in detectedObjects.objects) {
// val box = obj.boundingBox
// val name = "${categoryNames[obj.classificationCategory]}"
// val confidence =
// if (obj.classificationCategory != FirebaseVisionObject.CATEGORY_UNKNOWN) {
// val confidence: Int = obj.classificationConfidence!!.times(100).toInt()
// "$confidence%"
// } else {
// ""
// }
// list.add(BoxData("$name $confidence", box))
// }
// overlay.set(list)
// }
//
// Permissions
//
private val requiredPermissions: Array<String?>
get() {
return try {
val info = mainActivity?.packageManager
?.getPackageInfo(mainActivity?.packageName, PackageManager.GET_PERMISSIONS)
val ps = info?.requestedPermissions
if (ps != null && ps.isNotEmpty()) {
ps
} else {
arrayOfNulls(0)
}
} catch (e: Exception) {
arrayOfNulls(0)
}
}
private fun allPermissionsGranted(): Boolean {
for (permission in requiredPermissions) {
if (!isPermissionGranted(mainActivity!!, permission!!)) {
return false
}
}
return true
}
private fun getRuntimePermissions() {
val allNeededPermissions = arrayListOf<String>()
for (permission in requiredPermissions) {
if (!isPermissionGranted(mainActivity!!, permission!!)) {
allNeededPermissions.add(permission)
}
}
if (allNeededPermissions.isNotEmpty()) {
requestPermissions(allNeededPermissions.toTypedArray(), CAMERA_PERMISSION_REQUEST)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (allPermissionsGranted()) {
// view!!.postDelayed(
// {
// onStartCamera()
// },
// 2000L
// ) // TODO: remove this temp hack to sidestep crash when permissions were not available
}
}
companion object {
private const val CAMERA_PERMISSION_REQUEST = 1002
private fun isPermissionGranted(context: Context, permission: String): Boolean {
return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED
}
}
}

View File

@ -0,0 +1,20 @@
package cash.z.ecc.android.ui.scan
import androidx.lifecycle.ViewModel
import cash.z.wallet.sdk.Synchronizer
import cash.z.wallet.sdk.ext.twig
import javax.inject.Inject
class ScanViewModel @Inject constructor() : ViewModel() {
@Inject
lateinit var synchronizer: Synchronizer
suspend fun isNotValid(address: String) = synchronizer.validateAddress(address).isNotValid
override fun onCleared() {
super.onCleared()
twig("${javaClass.simpleName} cleared!")
}
}

View File

@ -5,24 +5,23 @@ import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.EditText
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.di.viewmodel.viewModel
import cash.z.ecc.android.ext.goneIf
import cash.z.ecc.android.ext.onClickNavBack
import cash.z.ecc.android.di.viewmodel.activityViewModel
import cash.z.ecc.android.ext.*
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
import javax.inject.Inject
import cash.z.wallet.sdk.ext.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.onErrorResumeNext
import kotlinx.coroutines.launch
class SendAddressFragment : BaseFragment<FragmentSendAddressBinding>(),
ClipboardManager.OnPrimaryClipChangedListener {
val sendViewModel: SendViewModel by viewModel()
val sendViewModel: SendViewModel by activityViewModel()
override fun inflate(inflater: LayoutInflater): FragmentSendAddressBinding =
FragmentSendAddressBinding.inflate(inflater)
@ -30,7 +29,7 @@ class SendAddressFragment : BaseFragment<FragmentSendAddressBinding>(),
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonNext.setOnClickListener {
onAddAddress()
onSubmit()
}
binding.backButtonHitArea.onClickNavBack()
binding.textBannerAction.setOnClickListener {
@ -39,20 +38,43 @@ class SendAddressFragment : BaseFragment<FragmentSendAddressBinding>(),
binding.textBannerMessage.setOnClickListener {
onPaste()
}
binding.textAmount.text = "Sending ${sendViewModel.zatoshiAmount.convertZatoshiToZecString(8)} ZEC"
binding.inputZcashAddress.setOnEditorActionListener { v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
onAddAddress()
true
} else {
false
// Apply View Model
if (sendViewModel.zatoshiAmount > 0L) {
sendViewModel.zatoshiAmount.convertZatoshiToZecString(8).let { amount ->
binding.inputZcashAmount.setText(amount)
binding.textAmount.text = "Sending $amount ZEC"
}
} else {
binding.inputZcashAmount.setText(null)
}
if (!sendViewModel.toAddress.isNullOrEmpty()){
binding.textAmount.text = "Send to ${sendViewModel.toAddress.abbreviatedAddress()}"
binding.inputZcashAddress.setText(sendViewModel.toAddress)
} else {
binding.inputZcashAddress.setText(null)
}
binding.inputZcashAddress.onEditorActionDone(::onSubmit)
binding.imageScanQr.onClickNavTo(R.id.action_nav_send_address_to_nav_scan)
}
private fun onAddAddress() {
private fun onSubmit(unused: EditText? = null) {
sendViewModel.toAddress = binding.inputZcashAddress.text.toString()
mainActivity?.navController?.navigate(R.id.action_nav_send_address_to_send_memo)
binding.inputZcashAmount.convertZecToZatoshi()?.let { sendViewModel.zatoshiAmount = it }
sendViewModel.validate().onFirstWith(resumedScope) {
if (it == null) {
mainActivity?.navController?.navigate(R.id.action_nav_send_address_to_send_memo)
} else {
resumedScope.launch {
binding.textAddressError.text = it
delay(1500L)
binding.textAddressError.text = ""
}
}
}
}
override fun onAttach(context: Context) {

View File

@ -6,6 +6,7 @@ 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.viewmodel.activityViewModel
import cash.z.ecc.android.di.viewmodel.viewModel
import cash.z.ecc.android.ext.goneIf
import cash.z.ecc.android.ext.onClickNavBack
@ -16,7 +17,7 @@ import kotlinx.coroutines.launch
class SendConfirmFragment : BaseFragment<FragmentSendConfirmBinding>() {
val sendViewModel: SendViewModel by viewModel()
val sendViewModel: SendViewModel by activityViewModel()
override fun inflate(inflater: LayoutInflater): FragmentSendConfirmBinding =
FragmentSendConfirmBinding.inflate(inflater)

View File

@ -7,6 +7,7 @@ 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.viewmodel.activityViewModel
import cash.z.ecc.android.di.viewmodel.viewModel
import cash.z.ecc.android.ext.goneIf
import cash.z.ecc.android.ui.base.BaseFragment
@ -22,7 +23,7 @@ import kotlin.random.Random
class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
val sendViewModel: SendViewModel by viewModel()
val sendViewModel: SendViewModel by activityViewModel()
override fun inflate(inflater: LayoutInflater): FragmentSendFinalBinding =
FragmentSendFinalBinding.inflate(inflater)

View File

@ -6,13 +6,14 @@ 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.viewmodel.activityViewModel
import cash.z.ecc.android.di.viewmodel.viewModel
import cash.z.ecc.android.ext.onClickNavBack
import cash.z.ecc.android.ui.base.BaseFragment
class SendMemoFragment : BaseFragment<FragmentSendMemoBinding>() {
val sendViewModel: SendViewModel by viewModel()
val sendViewModel: SendViewModel by activityViewModel()
override fun inflate(inflater: LayoutInflater): FragmentSendMemoBinding =
FragmentSendMemoBinding.inflate(inflater)

View File

@ -6,8 +6,10 @@ import cash.z.ecc.android.ui.setup.WalletSetupViewModel
import cash.z.wallet.sdk.Initializer
import cash.z.wallet.sdk.Synchronizer
import cash.z.wallet.sdk.entity.PendingTransaction
import cash.z.wallet.sdk.ext.ZcashSdk
import cash.z.wallet.sdk.ext.twig
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onEach
import javax.inject.Inject
@ -36,6 +38,19 @@ class SendViewModel @Inject constructor() : ViewModel() {
}
}
fun validate() = flow<String?> {
when {
synchronizer.validateAddress(toAddress).isNotValid -> {
emit("Please enter a valid address")
}
zatoshiAmount < ZcashSdk.MINERS_FEE_ZATOSHI -> {
emit("Please enter a larger amount")
}
else -> emit(null)
}
}
var toAddress: String = ""
var memo: String = ""
var zatoshiAmount: Long = -1L

View File

@ -0,0 +1,6 @@
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator"
android:fromAlpha="0.0"
android:toAlpha="1.0"
android:duration="200"
/>

View File

@ -0,0 +1,6 @@
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator"
android:fromAlpha="0.0"
android:toAlpha="1.0"
android:duration="700"
/>

View File

@ -0,0 +1,6 @@
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator"
android:fromAlpha="1.0"
android:toAlpha="0.0"
android:duration="200"
/>

View File

@ -0,0 +1,6 @@
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator"
android:fromAlpha="1.0"
android:toAlpha="0.0"
android:duration="250"
/>

View File

@ -0,0 +1,6 @@
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator"
android:fromAlpha="1.0"
android:toAlpha="0.0"
android:duration="300"
/>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="286dp"
android:height="285dp"
android:viewportWidth="286"
android:viewportHeight="285">
<path
android:pathData="M13.406,0L272.594,0L286,13.364L286,270.914L272.594,285L13.406,285L0,271.641L0,13.364L13.406,0Z"
android:strokeWidth="1"
android:fillColor="#2C2C2F"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
</vector>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="52dp"
android:height="53dp"
android:viewportWidth="52"
android:viewportHeight="53">
<path
android:pathData="M0.0488,0l0,33.502l19.0122,18.7646l32.4937,0l0,-11.9263l-27.7486,0l-11.1138,-11.5889l0,-28.7515z"
android:strokeWidth="1"
android:fillColor="#FFFFFF"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>

View File

@ -0,0 +1,36 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="279dp"
android:height="280dp"
android:viewportWidth="279"
android:viewportHeight="280">
<path
android:pathData="M18.977,6L7,17.981L7,260.023L18.977,272L261.008,272L273,259.355L273,17.981L261.023,6L18.977,6Z"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:fillType="evenOdd"/>
<path
android:pathData="M1,227l0,33.502l19.012,18.765l32.494,0l0,-11.926l-27.749,0l-11.114,-11.589l0,-28.751z"
android:strokeWidth="1"
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M226.62,278.886l33.502,-0l18.765,-19.012l0,-32.494l-11.926,-0l0,27.749l-11.589,11.114l-28.751,-0z"
android:strokeWidth="1"
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M278.506,52.267l-0,-33.502l-19.012,-18.765l-32.494,-0l-0,11.926l27.749,-0l11.114,11.589l-0,28.751z"
android:strokeWidth="1"
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M52.886,0.38l-33.502,0l-18.765,19.012l-0,32.494l11.926,0l-0,-27.749l11.589,-11.114l28.751,0z"
android:strokeWidth="1"
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</vector>

View File

@ -0,0 +1,48 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:height="726dp"
android:viewportHeight="726"
android:viewportWidth="412"
android:width="412dp">
<path
android:fillAlpha="0.6"
android:fillColor="#000000"
android:fillType="evenOdd"
android:pathData="M412,0L412,732L0,732L0,0L412,0ZM351,135L61,135L46,150.005L46,440L61,455L351,455L366,439.184L366,150.005L351,135Z"
android:strokeAlpha="0.5108352"
android:strokeColor="#00000000"
android:strokeWidth="1" />
<group android:scaleX="0.5" android:scaleY="0.5">
<path
android:fillColor="#00000000"
android:fillType="evenOdd"
android:pathData="M61.414,136L47,150.419L47,439.586L61.414,454L350.57,454L365,438.785L365,150.419L350.586,136L61.414,136Z"
android:strokeColor="#FFFFFF"
android:strokeWidth="2" />
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M41.049,407l0,33.502l19.012,18.765l32.494,0l0,-11.926l-27.749,0l-11.114,-11.589l0,-28.751z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M319,460.451l33.502,-0l18.765,-19.012l0,-32.494l-11.926,-0l0,27.749l-11.589,11.114l-28.751,-0z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M371.218,182.451l-0,-33.502l-19.012,-18.765l-32.494,-0l-0,11.926l27.749,-0l11.114,11.589l-0,28.751z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M93.267,129l-33.502,0l-18.765,19.012l-0,32.494l11.926,0l-0,-27.749l11.589,-11.114l28.751,0z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</group>
</vector>

View File

@ -0,0 +1,44 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="412dp"
android:height="726dp"
android:viewportWidth="412"
android:viewportHeight="726">
<path
android:pathData="M412,0L412,732L0,732L0,0L412,0ZM327.484,157L85.516,157L73,169.473L73,410.531L85.516,423L327.484,423L340,409.853L340,169.473L327.484,157Z"
android:strokeAlpha="0.5108352"
android:strokeWidth="1"
android:fillColor="#000000"
android:fillType="evenOdd"
android:strokeColor="#00000000"
android:fillAlpha="0.6"/>
<path
android:pathData="M84.977,157L73,168.981L73,411.023L84.977,423L327.008,423L339,410.355L339,168.981L327.023,157L84.977,157Z"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#FFFFFF"
android:fillType="evenOdd"/>
<path
android:pathData="M67,378l0,33.502l19.012,18.765l32.494,0l0,-11.926l-27.749,0l-11.114,-11.589l0,-28.751z"
android:strokeWidth="1"
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M292.62,429.886l33.502,-0l18.765,-19.012l0,-32.494l-11.926,-0l0,27.749l-11.589,11.114l-28.751,-0z"
android:strokeWidth="1"
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M344.506,203.267l-0,-33.502l-19.012,-18.765l-32.494,-0l-0,11.926l27.749,-0l11.114,11.589l-0,28.751z"
android:strokeWidth="1"
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M118.886,151.38l-33.502,0l-18.765,19.012l-0,32.494l11.926,0l-0,-27.749l11.589,-11.114l28.751,0z"
android:strokeWidth="1"
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</vector>

View File

@ -0,0 +1,24 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="451dp"
android:height="451dp"
android:viewportWidth="451"
android:viewportHeight="451">
<path
android:pathData="M225.4,0C101.11,0 0,101.11 0,225.39C0,349.67 101.11,450.79 225.4,450.79C349.69,450.79 450.79,349.68 450.79,225.39C450.79,101.1 349.68,0 225.4,0ZM225.4,401.79C128.131,401.79 49,322.659 49,225.39C49,128.122 128.131,49 225.4,49C322.668,49 401.79,128.131 401.79,225.39C401.79,322.65 322.659,401.79 225.4,401.79Z"
android:strokeWidth="1"
android:fillColor="#ffffff"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M225.4,225.39m-177.65,0a177.65,177.65 0,1 1,355.3 0a177.65,177.65 0,1 1,-355.3 0"
android:strokeWidth="1"
android:fillColor="#FFB727"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
<path
android:pathData="M305.78,155.13l0,-34.38l-61.5,0l0,-37.73l-37.77,0l0,37.73l-61.49,0l0,45.54l95.33,0l-77.96,107.29l-17.37,22.08l0,34.38l61.49,0l0,37.61l4.53,0l0,0.16l28.71,0l0,-0.16l4.53,0l0,-37.61l61.5,0l0,-45.54l-95.34,0l77.96,-107.29z"
android:strokeWidth="1"
android:fillColor="#231F20"
android:fillType="nonZero"
android:strokeColor="#00000000"/>
</vector>

View File

@ -1,240 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
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:fillViewport="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_receive_fragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/background_home"
tools:context=".ui.fragment.ReceiveFragment">
<!-- 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" />
<!-- Shield Background -->
<ImageView
android:id="@+id/image_shield"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="fitXY"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="H,1:1.1676"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/back_button"
app:layout_constraintVertical_bias="0.08"
app:layout_constraintWidth_default="percent"
app:layout_constraintWidth_percent="0.84988"
app:srcCompat="@drawable/ic_shield" />
<!-- QR code placeholder -->
<ImageView
android:id="@+id/receive_qr_code"
android:layout_width="0dp"
android:layout_height="0dp"
android:onClick="copyAddress"
android:scaleType="fitCenter"
app:layout_constraintBottom_toBottomOf="@id/image_shield"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/image_shield"
app:layout_constraintVertical_bias="0.272"
app:layout_constraintWidth_default="percent"
app:layout_constraintWidth_percent="0.59" />
<ImageView
android:layout_width="0dp"
android:layout_height="0dp"
android:onClick="copyAddress"
app:layout_constraintBottom_toBottomOf="@id/receive_qr_code"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintEnd_toEndOf="@id/receive_qr_code"
app:layout_constraintStart_toStartOf="@id/receive_qr_code"
app:layout_constraintTop_toTopOf="@id/receive_qr_code"
app:layout_constraintWidth_default="percent"
app:layout_constraintWidth_percent="0.12"
app:srcCompat="@drawable/ic_zcash_white" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/receive_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:backgroundTint="@color/colorPrimary"
app:layout_constraintBottom_toBottomOf="@id/image_shield"
app:layout_constraintEnd_toEndOf="@+id/image_shield"
app:layout_constraintStart_toStartOf="@+id/image_shield"
app:layout_constraintTop_toTopOf="@id/image_shield"
app:layout_constraintVertical_bias="0.924">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Zcash.TextAppearance.Headline6"
android:gravity="center"
android:padding="16dp"
android:text="@string/receive_address_title"
android:textColor="@color/text_dark" />
</com.google.android.material.card.MaterialCardView>
<!-- Address parts -->
<com.google.android.material.card.MaterialCardView
android:id="@+id/receive_address_background"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:backgroundTint="#282828"
app:layout_constraintEnd_toEndOf="@id/image_shield"
app:layout_constraintStart_toStartOf="@id/image_shield"
app:layout_constraintTop_toBottomOf="@id/image_shield">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:backgroundTint="#171717"
android:elevation="0dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/receive_address_parts"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:foregroundGravity="center"
android:onClick="copyAddress"
android:padding="16dp">
<TextView
android:id="@+id/text_address_part_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Zcash.TextAppearance.AddressPart"
android:text=" ztestsaplin"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/text_address_part_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
style="@style/Zcash.TextAppearance.AddressPart"
android:text=" jceuu9s2p6t"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_5"
app:layout_constraintStart_toStartOf="@id/text_address_part_1"
app:layout_constraintTop_toBottomOf="@id/text_address_part_1" />
<TextView
android:id="@+id/text_address_part_5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
style="@style/Zcash.TextAppearance.AddressPart"
android:text=" 7u7uarqls7d"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_7"
app:layout_constraintStart_toStartOf="@id/text_address_part_1"
app:layout_constraintTop_toBottomOf="@id/text_address_part_3" />
<TextView
android:id="@+id/text_address_part_7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
style="@style/Zcash.TextAppearance.AddressPart"
android:text=" rzq85xggu56"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@id/text_address_part_1"
app:layout_constraintTop_toBottomOf="@id/text_address_part_5" />
<TextView
android:id="@+id/text_address_part_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
style="@style/Zcash.TextAppearance.AddressPart"
android:text=" g1mwjzlg62j"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_4"
app:layout_constraintStart_toEndOf="@id/barrier_left_address_column"
app:layout_constraintTop_toTopOf="@id/text_address_part_1" />
<TextView
android:id="@+id/text_address_part_4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
style="@style/Zcash.TextAppearance.AddressPart"
android:text=" wns6qxwec6v"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_6"
app:layout_constraintStart_toStartOf="@id/text_address_part_2"
app:layout_constraintTop_toBottomOf="@id/text_address_part_2" />
<TextView
android:id="@+id/text_address_part_6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
style="@style/Zcash.TextAppearance.AddressPart"
android:text=" gtg3tpgqxjd"
app:layout_constraintBottom_toTopOf="@+id/text_address_part_8"
app:layout_constraintStart_toStartOf="@id/text_address_part_2"
app:layout_constraintTop_toBottomOf="@id/text_address_part_4" />
<TextView
android:id="@+id/text_address_part_8"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
style="@style/Zcash.TextAppearance.AddressPart"
android:text=" k904xderng6"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@id/text_address_part_2"
app:layout_constraintTop_toBottomOf="@id/text_address_part_6" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier_left_address_column"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="150dp"
android:layout_marginRight="150dp"
android:padding="150dp"
app:barrierDirection="end"
app:constraint_referenced_ids="text_address_part_1,text_address_part_3,text_address_part_5,text_address_part_7" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>

View File

@ -0,0 +1,180 @@
<?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">
<!-- Spacers -->
<Space
android:id="@+id/spacer_bottom_left"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintWidth_percent="0.116"
tools:background="@color/zcashRed" />
<Space
android:id="@+id/spacer_bottom_right"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintWidth_percent="0.116"
tools:background="@color/zcashRed" />
<Space
android:id="@+id/spacer_title"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/background_qr"
app:layout_constraintWidth_percent="0.116"
tools:background="@color/zcashRed" />
<!-- <ImageView-->
<!-- android:id="@+id/scan_frame"-->
<!-- android:layout_width="0dp"-->
<!-- android:layout_height="0dp"-->
<!-- android:scaleType="centerCrop"-->
<!-- android:src="@drawable/ic_scan_frame"-->
<!-- app:layout_constraintDimensionRatio="H,1:1"-->
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
<!-- app:layout_constraintEnd_toStartOf="@id/spacer_bottom_right"-->
<!-- app:layout_constraintStart_toEndOf="@id/spacer_bottom_left"-->
<!-- app:layout_constraintTop_toTopOf="parent"-->
<!-- app:layout_constraintVertical_bias="0.3" />-->
<ImageView
android:id="@+id/background_qr"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="fitXY"
android:src="@drawable/ic_background_qr"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/spacer_bottom_right"
app:layout_constraintStart_toEndOf="@id/spacer_bottom_left"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.3" />
<!-- QR code placeholder -->
<ImageView
android:id="@+id/receive_qr_code"
android:layout_width="0dp"
android:layout_height="0dp"
android:onClick="copyAddress"
android:scaleType="fitCenter"
tools:visibility="visible"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintWidth_percent="0.625"
app:layout_constraintStart_toStartOf="@id/background_qr"
app:layout_constraintEnd_toEndOf="@id/background_qr"
app:layout_constraintTop_toTopOf="@id/background_qr"
app:layout_constraintBottom_toBottomOf="@id/background_qr"
tools:background="@color/zcashWhite" />
<ImageView
android:id="@+id/icon_qr_logo"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:onClick="copyAddress"
android:scaleType="fitCenter"
android:src="@drawable/ic_zcash_primary"
app:layout_constraintBottom_toBottomOf="@id/receive_qr_code"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintEnd_toEndOf="@id/receive_qr_code"
app:layout_constraintStart_toStartOf="@id/receive_qr_code"
app:layout_constraintTop_toTopOf="@id/receive_qr_code"
app:layout_constraintWidth_percent="0.14" />
<TextView
android:id="@+id/receive_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Your Address"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/text_light"
android:textSize="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/spacer_title" />
<TextView
android:id="@+id/receive_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/Zcash.TextAppearance.AddressPart"
android:textColor="@color/text_light"
android:textSize="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/receive_title"
tools:text="ztcs23cs...zt249skf" />
<ImageView
android:id="@+id/icon_copy"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:onClick="copyAddress"
android:scaleType="fitCenter"
android:src="@drawable/ic_content_copy"
app:layout_constraintBottom_toBottomOf="@id/receive_address"
app:layout_constraintStart_toEndOf="@id/receive_address"
app:layout_constraintTop_toTopOf="@id/receive_address" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_scan"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="@style/Zcash.Button"
android:gravity="center"
android:padding="12dp"
android:text="Scan Recipient Address"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="#000000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/spacer_bottom_right"
app:layout_constraintStart_toEndOf="@id/spacer_bottom_left"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.9226" />
<!-- 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" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,220 @@
<?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">
<!-- Guidelines -->
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_overlay_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.136" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_overlay_end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.864" />
<!-- Spacers -->
<Space
android:id="@+id/spacer_bottom_left"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintWidth_percent="0.116"
tools:background="@color/zcashRed" />
<Space
android:id="@+id/spacer_bottom_right"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintWidth_percent="0.116"
tools:background="@color/zcashRed" />
<androidx.camera.view.PreviewView
android:id="@+id/preview"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- Overlays -->
<View
android:id="@+id/overlay_bottom"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/scan_overlay_background"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/spacer_overlay" />
<View
android:id="@+id/overlay_end"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/scan_overlay_background"
app:layout_constraintBottom_toBottomOf="@id/spacer_overlay"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/spacer_overlay"
app:layout_constraintTop_toTopOf="@id/spacer_overlay" />
<View
android:id="@+id/overlay_start"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/scan_overlay_background"
app:layout_constraintBottom_toBottomOf="@id/spacer_overlay"
app:layout_constraintEnd_toStartOf="@id/spacer_overlay"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/spacer_overlay" />
<View
android:id="@+id/overlay_top"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/scan_overlay_background"
app:layout_constraintBottom_toTopOf="@id/spacer_overlay"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- being anal with these -->
<View
android:id="@+id/overlay_top_left_notch"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/scan_overlay_background"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintStart_toEndOf="@id/overlay_start"
app:layout_constraintTop_toBottomOf="@id/overlay_top"
app:layout_constraintWidth_percent="0.02" />
<View
android:id="@+id/overlay_top_right_notch"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/scan_overlay_background"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintEnd_toStartOf="@id/overlay_end"
app:layout_constraintTop_toBottomOf="@id/overlay_top"
app:layout_constraintWidth_percent="0.02" />
<View
android:id="@+id/overlay_bottom_left_notch"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/scan_overlay_background"
app:layout_constraintBottom_toTopOf="@id/overlay_bottom"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintStart_toEndOf="@id/overlay_start"
app:layout_constraintWidth_percent="0.02" />
<View
android:id="@+id/overlay_bottom_right_notch"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/scan_overlay_background"
app:layout_constraintBottom_toTopOf="@id/overlay_bottom"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintEnd_toStartOf="@id/overlay_end"
app:layout_constraintWidth_percent="0.02" />
<ImageView
android:id="@+id/scan_frame"
android:layout_width="0dp"
android:layout_height="0dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_scan_frame"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintEnd_toStartOf="@id/spacer_bottom_right"
app:layout_constraintStart_toEndOf="@id/spacer_bottom_left"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.3" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_receive"
android:layout_width="0dp"
android:layout_height="0dp"
style="@style/Zcash.Button.White"
android:gravity="center"
android:padding="12dp"
android:text="Switch to Your Address"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="#000000"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/spacer_bottom_right"
app:layout_constraintHeight_percent="0.2"
app:layout_constraintStart_toEndOf="@id/spacer_bottom_left"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.9226" />
<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_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
style="@style/TextAppearance.MaterialComponents.Headline6"
android:autoSizeTextType="uniform"
android:maxLines="1"
android:text="Scan Recipient Address"
android:textColor="@color/text_light"
app:layout_constraintBottom_toBottomOf="@id/back_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/back_button_hit_area"
app:layout_constraintTop_toTopOf="@id/back_button" />
<Space
android:id="@+id/spacer_overlay"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintEnd_toStartOf="@id/guideline_overlay_end"
app:layout_constraintStart_toEndOf="@id/guideline_overlay_start"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.305"
tools:background="#60ff0000" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -69,20 +69,39 @@
app:layout_constraintWidth_percent="0.84"
app:layout_constraintVertical_bias="0.2"/>
<!-- Input: Amount -->
<EditText
android:id="@+id/input_zcash_amount"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:hint="@string/send_hint_input_zcash_amount"
android:imeOptions="actionDone"
android:inputType="numberDecimal"
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_toBottomOf="@id/input_zcash_address"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintWidth_percent="0.84"/>
<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" />
android:textSize="14dp"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="@+id/button_next"
app:layout_constraintStart_toStartOf="@+id/input_zcash_amount"
app:layout_constraintTop_toTopOf="@+id/button_next"
tools:text="Please enter a larger amount of money" />
<!-- Scan QR code -->
<ImageView
@ -107,7 +126,7 @@
android:text="Next"
android:textColor="@color/text_dark"
app:layout_constraintEnd_toEndOf="@+id/input_zcash_address"
app:layout_constraintTop_toBottomOf="@+id/input_zcash_address" />
app:layout_constraintTop_toBottomOf="@+id/input_zcash_amount" />
<!-- -->
<!-- Banner -->
@ -127,9 +146,10 @@
android:textColor="@color/text_light"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@+id/input_zcash_address"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="@+id/input_zcash_address"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.45"/>
app:layout_constraintTop_toBottomOf="@id/button_next"
app:layout_constraintVertical_bias="0.07" />
<TextView
android:id="@+id/text_banner_action"

View File

@ -18,27 +18,111 @@
app:destination="@id/nav_detail" />
<action
android:id="@+id/action_nav_home_to_create_wallet"
app:destination="@id/wallet_setup_navigation" />
app:destination="@id/nav_landing" />
<action
android:id="@+id/action_nav_home_to_send"
app:destination="@id/send_navigation" />
app:destination="@id/nav_send_address" />
<action
android:id="@+id/action_nav_home_to_nav_scan"
app:destination="@id/nav_scan" />
</fragment>
<fragment
android:id="@+id/nav_receive"
android:name="cash.z.ecc.android.ui.receive.ReceiveFragment"
tools:layout="@layout/fragment_receive" />
tools:layout="@layout/fragment_receive_new" >
<action
android:id="@+id/action_nav_receive_to_nav_scan"
app:destination="@id/nav_scan"
app:popUpTo="@id/nav_scan"
app:popUpToInclusive="true"
app:exitAnim="@anim/anim_fade_out_address"
app:enterAnim="@anim/anim_fade_in_scanner"/>
</fragment>
<fragment
android:id="@+id/nav_scan"
android:name="cash.z.ecc.android.ui.scan.ScanFragment"
tools:layout="@layout/fragment_scan">
<action
android:id="@+id/action_nav_scan_to_nav_send_address"
app:destination="@id/nav_send_address"
app:popUpTo="@id/nav_send_address"/>
<action
android:id="@+id/action_nav_scan_to_nav_receive"
app:popUpTo="@id/nav_receive"
app:popUpToInclusive="true"
app:destination="@id/nav_receive"
app:exitAnim="@anim/anim_fade_out_medium"/>
</fragment>
<fragment
android:id="@+id/nav_detail"
android:name="cash.z.ecc.android.ui.detail.WalletDetailFragment"
tools:layout="@layout/fragment_detail" >
<action
android:id="@+id/action_nav_detail_to_backup_wallet"
app:destination="@id/wallet_setup_navigation" />
app:destination="@id/nav_backup" />
</fragment>
<include app:graph="@navigation/wallet_setup_navigation" />
<include app:graph="@navigation/send_navigation" />
<!-- -->
<!-- Send Navigation -->
<!-- -->
<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"/>
<action
android:id="@+id/action_nav_send_address_to_nav_scan"
app:destination="@id/nav_scan" />
</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>
<!-- -->
<!-- Wallet Setup Navigation -->
<!-- -->
<fragment
android:id="@+id/nav_landing"
android:name="cash.z.ecc.android.ui.setup.LandingFragment"
tools:layout="@layout/fragment_landing" >
<action
android:id="@+id/action_nav_landing_to_nav_backup"
app:destination="@id/nav_backup"
app:popUpTo="@id/nav_landing"
app:popUpToInclusive="true"/>
</fragment>
<fragment
android:id="@+id/nav_backup"
android:name="cash.z.ecc.android.ui.setup.BackupFragment"
tools:layout="@layout/fragment_backup" >
</fragment>
</navigation>

View File

@ -1,42 +0,0 @@
<?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

@ -1,26 +0,0 @@
<?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/wallet_setup_navigation"
app:startDestination="@+id/nav_landing">
<fragment
android:id="@+id/nav_landing"
android:name="cash.z.ecc.android.ui.setup.LandingFragment"
tools:layout="@layout/fragment_landing" >
<action
android:id="@+id/action_nav_landing_to_nav_backup"
app:destination="@id/nav_backup"
app:popUpTo="@id/nav_landing"
app:popUpToInclusive="true"/>
</fragment>
<fragment
android:id="@+id/nav_backup"
android:name="cash.z.ecc.android.ui.setup.BackupFragment"
tools:layout="@layout/fragment_backup" >
</fragment>
</navigation>

View File

@ -55,6 +55,7 @@
but have a more useful name for use in code -->
<color name="background_banner">@color/zcashBlack_dark</color>
<color name="scan_overlay_background">@color/zcashBlack_87</color>
<!-- text -->
<color name="text_light">#FFFFFF</color>

View File

@ -7,4 +7,5 @@
<!-- Send Flow -->
<string name="send_hint_input_zcash_address">Enter a shielded Zcash address</string>
<string name="send_hint_input_zcash_amount">Enter an amount to send</string>
</resources>

View File

@ -68,11 +68,11 @@
<!-- Shape Appearances -->
<style name="Zcash.ShapeAppearance.SmallComponent" parent="ShapeAppearance.MaterialComponents.SmallComponent">
<item name="cornerFamily">cut</item>
<item name="cornerSize">10dp</item>
<item name="cornerFamily">rounded</item>
<item name="cornerSize">20dp</item>
</style>
<style name="Zcash.ShapeAppearance.MediumComponent" parent="ShapeAppearance.MaterialComponents.MediumComponent">
<item name="cornerFamily">cut</item>
<item name="cornerSize">12dp</item>
<item name="cornerFamily">rounded</item>
<item name="cornerSize">24dp</item>
</style>
</resources>

View File

@ -6,7 +6,8 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.6.0-beta04'
classpath 'com.android.tools.build:gradle:3.6.0-rc01'
classpath 'com.google.gms:google-services:4.3.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${Deps.kotlinVersion}"
}
}

View File

@ -1,6 +1,6 @@
#Sun Nov 24 16:57:24 EST 2019
#Tue Jan 07 12:00:21 EST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip