Added QR Scanner UI
This commit is contained in:
parent
4922d690e9
commit
c42a0063c2
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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()))
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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!")
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
/>
|
|
@ -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"
|
||||
/>
|
|
@ -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"
|
||||
/>
|
|
@ -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"
|
||||
/>
|
|
@ -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"
|
||||
/>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue