Added QR Scanner UI

This commit is contained in:
Kevin Gorham 2020-01-08 03:45:45 -05:00
parent 4922d690e9
commit c42a0063c2
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
23 changed files with 809 additions and 32 deletions

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

@ -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,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

@ -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

@ -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>