Merge pull request #96 from zcash/release/internal-20200325
Internal ECC release 2020-03-25
This commit is contained in:
commit
337a361ef1
|
@ -11,7 +11,7 @@ apply plugin: 'com.google.firebase.firebase-perf'
|
|||
|
||||
archivesBaseName = 'zcash-android-wallet'
|
||||
group = 'cash.z.ecc.android'
|
||||
version = '1.0.0-alpha23'
|
||||
version = '1.0.0-alpha25'
|
||||
|
||||
android {
|
||||
compileSdkVersion Deps.compileSdkVersion
|
||||
|
@ -21,7 +21,7 @@ android {
|
|||
applicationId 'cash.z.ecc.android'
|
||||
minSdkVersion Deps.minSdkVersion
|
||||
targetSdkVersion Deps.targetSdkVersion
|
||||
versionCode = 1_00_00_023
|
||||
versionCode = 1_00_00_025
|
||||
// last digits are alpha(0XX) beta(2XX) rc(4XX) release(8XX) dev(9XX). Ex: 1_08_04_401 is an release candidate build of version 1.8.4 and 1_08_04_800 would be the final release.
|
||||
versionName = "$version"
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
|
|
@ -17,7 +17,7 @@ class SynchronizerModule {
|
|||
@Provides
|
||||
@SynchronizerScope
|
||||
fun provideSynchronizer(appContext: Context, initializer: Initializer): Synchronizer {
|
||||
return Synchronizer(appContext, initializer)
|
||||
return Synchronizer(initializer)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import android.text.Spanned
|
|||
import android.text.style.ForegroundColorSpan
|
||||
import androidx.core.text.toSpannable
|
||||
|
||||
fun String.toColoredSpan(colorResId: Int, coloredPortion: String): Spannable {
|
||||
fun CharSequence.toColoredSpan(colorResId: Int, coloredPortion: String): Spannable {
|
||||
return toSpannable().apply {
|
||||
val start = this@toColoredSpan.indexOf(coloredPortion)
|
||||
setSpan(ForegroundColorSpan(colorResId.toAppColor()), start, start + coloredPortion.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
|
|
|
@ -22,6 +22,10 @@ fun View.disabledIf(isDisabled: Boolean) {
|
|||
isEnabled = !isDisabled
|
||||
}
|
||||
|
||||
fun View.transparentIf(isTransparent: Boolean) {
|
||||
alpha = if (isTransparent) 0.0f else 1.0f
|
||||
}
|
||||
|
||||
fun View.onClickNavTo(navResId: Int, block: (() -> Any) = {}) {
|
||||
setOnClickListener {
|
||||
block()
|
||||
|
|
|
@ -43,6 +43,12 @@ object Report {
|
|||
object ImportCompleted : Restore("importcompleted", 50)
|
||||
object Success : Restore("success", 100)
|
||||
}
|
||||
|
||||
sealed class UserFeedback(stepName: String, step: Int, vararg properties: Pair<String, Any>) : Feedback.Funnel("feedback", stepName, step, *properties) {
|
||||
object Started : UserFeedback("started", 0)
|
||||
object Cancelled : UserFeedback("cancelled", 1)
|
||||
class Submitted(rating: Int, question1: String, question2: String, question3: String) : UserFeedback("submitted", 100, "rating" to rating, "question1" to question1, "question2" to question2, "question3" to question3)
|
||||
}
|
||||
}
|
||||
|
||||
object Error {
|
||||
|
@ -84,6 +90,7 @@ object Report {
|
|||
DETAIL("wallet.detail"),
|
||||
LANDING,
|
||||
PROFILE,
|
||||
FEEDBACK,
|
||||
RECEIVE,
|
||||
RESTORE,
|
||||
SCAN,
|
||||
|
@ -120,6 +127,8 @@ object Report {
|
|||
PROFILE_VIEW_USER_LOGS("profile.view.user.logs"),
|
||||
PROFILE_VIEW_DEV_LOGS("profile.view.dev.logs"),
|
||||
PROFILE_SEND_FEEDBACK("profile.send.feedback"),
|
||||
FEEDBACK_CANCEL("feedback.cancel"),
|
||||
FEEDBACK_SUBMIT("feedback.submit"),
|
||||
RECEIVE_SCAN("receive.scan"),
|
||||
RECEIVE_BACK("receive.back"),
|
||||
RESTORE_DONE("restore.done"),
|
||||
|
|
|
@ -50,7 +50,6 @@ import javax.inject.Inject
|
|||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
|
||||
@Inject
|
||||
lateinit var feedback: Feedback
|
||||
|
||||
|
@ -324,6 +323,7 @@ class MainActivity : AppCompatActivity() {
|
|||
showSnackbar("Well, this is awkward. You denied permission for the camera.")
|
||||
}
|
||||
|
||||
// TODO: clean up this error handling
|
||||
private var ignoredErrors = 0
|
||||
private fun onProcessorError(error: Throwable?): Boolean {
|
||||
var notified = false
|
||||
|
@ -368,25 +368,25 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
if (!notified) {
|
||||
ignoredErrors++
|
||||
}
|
||||
if (ignoredErrors >= ZcashSdk.RETRIES) {
|
||||
if (dialog == null) {
|
||||
notified = true
|
||||
runOnUiThread {
|
||||
dialog = MaterialAlertDialogBuilder(this)
|
||||
.setTitle("Processor Error")
|
||||
.setMessage(error?.message ?: "Critical error while processing blocks!")
|
||||
.setCancelable(false)
|
||||
.setPositiveButton("Retry") { d, _ ->
|
||||
d.dismiss()
|
||||
dialog = null
|
||||
}
|
||||
.setNegativeButton("Exit") { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
throw error
|
||||
?: RuntimeException("Critical error while processing blocks and the user chose to exit.")
|
||||
}
|
||||
.show()
|
||||
if (ignoredErrors >= ZcashSdk.RETRIES) {
|
||||
if (dialog == null) {
|
||||
notified = true
|
||||
runOnUiThread {
|
||||
dialog = MaterialAlertDialogBuilder(this)
|
||||
.setTitle("Processor Error")
|
||||
.setMessage(error?.message ?: "Critical error while processing blocks!")
|
||||
.setCancelable(false)
|
||||
.setPositiveButton("Retry") { d, _ ->
|
||||
d.dismiss()
|
||||
dialog = null
|
||||
}
|
||||
.setNegativeButton("Exit") { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
throw error
|
||||
?: RuntimeException("Critical error while processing blocks and the user chose to exit.")
|
||||
}
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,15 @@ package cash.z.ecc.android.ui.detail
|
|||
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import cash.z.ecc.android.R
|
||||
import cash.z.ecc.android.ext.goneIf
|
||||
import cash.z.ecc.android.ext.toAppColor
|
||||
import cash.z.ecc.android.ui.MainActivity
|
||||
import cash.z.ecc.android.ui.send.SendViewModel
|
||||
import cash.z.ecc.android.ui.util.INCLUDE_MEMO_PREFIX
|
||||
import cash.z.ecc.android.ui.util.toUtf8Memo
|
||||
import cash.z.wallet.sdk.entity.ConfirmedTransaction
|
||||
import cash.z.wallet.sdk.ext.*
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
@ -21,6 +25,7 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
|||
private val bottomText = itemView.findViewById<TextView>(R.id.text_transaction_bottom)
|
||||
private val shieldIcon = itemView.findViewById<View>(R.id.image_shield)
|
||||
private val formatter = SimpleDateFormat("M/d h:mma", Locale.getDefault())
|
||||
private val addressRegex = """zs\d\w{65,}""".toRegex()
|
||||
|
||||
fun bindTo(transaction: T?) {
|
||||
|
||||
|
@ -29,13 +34,19 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
|||
var lineTwo: String = ""
|
||||
var amountZec: String = ""
|
||||
var amountDisplay: String = ""
|
||||
var amountColor: Int = 0
|
||||
var indicatorBackground: Int = 0
|
||||
var amountColor: Int = R.color.text_light_dimmed
|
||||
var lineOneColor: Int = R.color.text_light
|
||||
var lineTwoColor: Int = R.color.text_light_dimmed
|
||||
var indicatorBackground: Int = R.drawable.background_indicator_unknown
|
||||
|
||||
transaction?.apply {
|
||||
itemView.setOnClickListener {
|
||||
onTransactionClicked(this)
|
||||
}
|
||||
itemView.setOnLongClickListener {
|
||||
onTransactionLongPressed(this)
|
||||
true
|
||||
}
|
||||
amountZec = value.convertZatoshiToZecString()
|
||||
// TODO: these might be good extension functions
|
||||
val timestamp = formatter.format(blockTimeInSeconds * 1000L)
|
||||
|
@ -45,11 +56,16 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
|||
lineOne = "You paid ${toAddress?.toAbbreviatedAddress()}"
|
||||
lineTwo = if (isMined) "Sent $timestamp" else "Pending confirmation"
|
||||
amountDisplay = "- $amountZec"
|
||||
amountColor = R.color.zcashRed
|
||||
indicatorBackground = R.drawable.background_indicator_outbound
|
||||
if (isMined) {
|
||||
amountColor = R.color.zcashRed
|
||||
indicatorBackground = R.drawable.background_indicator_outbound
|
||||
} else {
|
||||
lineOneColor = R.color.text_light_dimmed
|
||||
lineTwoColor = R.color.text_light
|
||||
}
|
||||
}
|
||||
raw == null || raw?.isEmpty() == true -> {
|
||||
lineOne = "Unknown paid you"
|
||||
toAddress.isNullOrEmpty() && value > 0L && minedHeight > 0 -> {
|
||||
lineOne = getSender(transaction)
|
||||
lineTwo = "Received $timestamp"
|
||||
amountDisplay = "+ $amountZec"
|
||||
amountColor = R.color.zcashGreen
|
||||
|
@ -58,9 +74,10 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
|||
else -> {
|
||||
lineOne = "Unknown"
|
||||
lineTwo = "Unknown"
|
||||
amountDisplay = "$amountZec"
|
||||
amountColor = R.color.text_light
|
||||
}
|
||||
}
|
||||
|
||||
// sanitize amount
|
||||
if (value < ZcashSdk.MINERS_FEE_ZATOSHI) amountDisplay = "< 0.001"
|
||||
else if (amountZec.length > 10) { // 10 allows 3 digits to the left and 6 to the right of the decimal
|
||||
|
@ -73,17 +90,41 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
|||
bottomText.text = lineTwo
|
||||
amountText.text = amountDisplay
|
||||
amountText.setTextColor(amountColor.toAppColor())
|
||||
topText.setTextColor(lineOneColor.toAppColor())
|
||||
bottomText.setTextColor(lineTwoColor.toAppColor())
|
||||
val context = itemView.context
|
||||
indicator.background = context.resources.getDrawable(indicatorBackground)
|
||||
shieldIcon.goneIf((transaction?.raw != null || transaction?.expiryHeight != null) && !transaction?.toAddress.isShielded())
|
||||
}
|
||||
|
||||
private fun getSender(transaction: ConfirmedTransaction): String {
|
||||
val memo = transaction.memo.toUtf8Memo()
|
||||
return when {
|
||||
memo.contains(INCLUDE_MEMO_PREFIX) -> {
|
||||
val address = memo.split(INCLUDE_MEMO_PREFIX)[1].trim()
|
||||
"${address.toAbbreviatedAddress()} paid you"
|
||||
}
|
||||
memo.contains("eply to:") -> {
|
||||
val address = memo.split("eply to:")[1].trim()
|
||||
"${address.toAbbreviatedAddress()} paid you"
|
||||
}
|
||||
memo.contains("zs") -> {
|
||||
val who = extractAddress(memo)?.toAbbreviatedAddress() ?: "Unknown"
|
||||
"$who paid you"
|
||||
}
|
||||
else -> "Unknown paid you"
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractAddress(memo: String?) =
|
||||
addressRegex.findAll(memo ?: "").lastOrNull()?.value
|
||||
|
||||
private fun onTransactionClicked(transaction: ConfirmedTransaction) {
|
||||
val txId = transaction.rawTransactionId.toTxId()
|
||||
val detailsMessage: String = "Zatoshi amount: ${transaction.value}\n\n" +
|
||||
"Transaction: $txId" +
|
||||
"${if (transaction.toAddress != null) "\n\nto: ${transaction.toAddress}" else ""}" +
|
||||
"${if (transaction.memo != null) "\n\nmemo: \n${String(transaction.memo!!, Charset.forName("UTF-8"))}" else ""}"
|
||||
"${if (transaction.toAddress != null) "\n\nTo: ${transaction.toAddress}" else ""}" +
|
||||
"${if (transaction.memo != null) "\n\nMemo: \n${String(transaction.memo!!, Charset.forName("UTF-8"))}" else ""}"
|
||||
|
||||
MaterialAlertDialogBuilder(itemView.context)
|
||||
.setMessage(detailsMessage)
|
||||
|
@ -98,6 +139,12 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
|
|||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun onTransactionLongPressed(transaction: ConfirmedTransaction) {
|
||||
(transaction.toAddress ?: extractAddress(transaction.memo.toUtf8Memo()))?.let {
|
||||
(itemView.context as MainActivity).copyText(it, "Transaction Address")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ByteArray.toTxId(): String {
|
||||
|
|
|
@ -66,6 +66,7 @@ class WalletDetailFragment : BaseFragment<FragmentDetailBinding>() {
|
|||
adapter = TransactionAdapter()
|
||||
viewModel.transactions.collectWith(resumedScope) { onTransactionsUpdated(it) }
|
||||
binding.recyclerTransactions.adapter = adapter
|
||||
binding.recyclerTransactions.smoothScrollToPosition(0)
|
||||
}
|
||||
|
||||
private fun onTransactionsUpdated(transactions: PagedList<ConfirmedTransaction>) {
|
||||
|
|
|
@ -19,7 +19,8 @@ import cash.z.ecc.android.ui.send.SendViewModel
|
|||
import cash.z.ecc.android.ui.setup.WalletSetupViewModel
|
||||
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.NO_SEED
|
||||
import cash.z.wallet.sdk.Synchronizer
|
||||
import cash.z.wallet.sdk.Synchronizer.Status.SYNCED
|
||||
import cash.z.wallet.sdk.Synchronizer.Status.*
|
||||
import cash.z.wallet.sdk.block.CompactBlockProcessor
|
||||
import cash.z.wallet.sdk.ext.convertZatoshiToZecString
|
||||
import cash.z.wallet.sdk.ext.convertZecToZatoshi
|
||||
import cash.z.wallet.sdk.ext.safelyConvertToBigDecimal
|
||||
|
@ -92,7 +93,6 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||
buttonNumberPadBack.asKey()
|
||||
)
|
||||
hitAreaReceive.onClickNavTo(R.id.action_nav_home_to_nav_profile) { tapped(HOME_PROFILE) }
|
||||
iconDetail.onClickNavTo(R.id.action_nav_home_to_nav_detail) { tapped(HOME_DETAIL) }
|
||||
textDetail.onClickNavTo(R.id.action_nav_home_to_nav_detail) { tapped(HOME_DETAIL) }
|
||||
hitAreaScan.setOnClickListener {
|
||||
mainActivity?.maybeOpenScan().also { tapped(HOME_SCAN) }
|
||||
|
@ -195,8 +195,8 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||
}
|
||||
|
||||
fun setProgress(uiModel: HomeViewModel.UiModel) {
|
||||
if (!uiModel.processorInfo.hasData) {
|
||||
twig("Warning: ignoring progress update because the processor has not started.")
|
||||
if (!uiModel.processorInfo.hasData && !uiModel.isDisconnected) {
|
||||
twig("Warning: ignoring progress update because the processor is still starting.")
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -207,28 +207,21 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||
}
|
||||
|
||||
val sendText = when {
|
||||
uiModel.status == DISCONNECTED -> "Reconnecting . . ."
|
||||
uiModel.isSynced -> if (uiModel.hasFunds) "SEND AMOUNT" else "NO FUNDS AVAILABLE"
|
||||
uiModel.status == Synchronizer.Status.DISCONNECTED -> "DISCONNECTED"
|
||||
uiModel.status == Synchronizer.Status.STOPPED -> "IDLE"
|
||||
uiModel.status == STOPPED -> "IDLE"
|
||||
uiModel.isDownloading -> "Downloading . . . ${snake.downloadProgress}%"
|
||||
uiModel.isValidating -> "Validating . . ."
|
||||
uiModel.isScanning -> "Scanning . . . ${snake.scanProgress}%"
|
||||
else -> "Updating"
|
||||
}
|
||||
|
||||
// binding.lottieButtonLoading.progress = if (uiModel.isSynced) 1.0f else uiModel.totalProgress * 0.82f // line fully closes at 82% mark
|
||||
binding.buttonSendAmount.text = sendText
|
||||
// twig("Lottie progress set to ${binding.lottieButtonLoading.progress} (isSynced? ${uiModel.isSynced})")
|
||||
twig("Send button set to: $sendText")
|
||||
|
||||
val resId = if (uiModel.isSynced) R.color.selector_button_text_dark else R.color.selector_button_text_light
|
||||
binding.buttonSendAmount.setTextColor(resources.getColorStateList(resId))
|
||||
|
||||
// if (uiModel.status == DISCONNECTED || uiModel.status == STOPPED) {
|
||||
// binding.buttonSendAmount.backgroundTintList = ColorStateList.valueOf(resources.getColor(R.color.zcashGray))
|
||||
// } else {
|
||||
// binding.buttonSendAmount.backgroundTintList = null
|
||||
// }
|
||||
binding.lottieButtonLoading.invisibleIf(uiModel.isDisconnected)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -243,10 +236,13 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||
}
|
||||
|
||||
fun setAvailable(availableBalance: Long = -1L, totalBalance: Long = -1L) {
|
||||
val availableString = if (availableBalance < 0) "Updating" else availableBalance.convertZatoshiToZecString()
|
||||
val missingBalance = availableBalance < 0
|
||||
val availableString = if (missingBalance) "Updating" else availableBalance.convertZatoshiToZecString()
|
||||
binding.textBalanceAvailable.text = availableString
|
||||
binding.textBalanceAvailable.transparentIf(missingBalance)
|
||||
binding.labelBalance.transparentIf(missingBalance)
|
||||
binding.textBalanceDescription.apply {
|
||||
goneIf(availableBalance < 0)
|
||||
goneIf(missingBalance)
|
||||
text = if (availableBalance != -1L && (availableBalance < totalBalance)) {
|
||||
val change = (totalBalance - availableBalance).convertZatoshiToZecString()
|
||||
"(expecting +$change ZEC)".toColoredSpan(R.color.text_light, "+$change")
|
||||
|
@ -275,12 +271,10 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||
|
||||
private fun onModelUpdated(old: HomeViewModel.UiModel?, new: HomeViewModel.UiModel) {
|
||||
logUpdate(old, new)
|
||||
if (binding.lottieButtonLoading.visibility != View.VISIBLE) binding.lottieButtonLoading.visibility = View.VISIBLE
|
||||
uiModel = new
|
||||
if (old?.pendingSend != new.pendingSend) {
|
||||
setSendAmount(new.pendingSend)
|
||||
}
|
||||
// TODO: handle stopped and disconnected flows
|
||||
setProgress(uiModel) // TODO: we may not need to separate anymore
|
||||
// if (new.status = SYNCING) onSyncing(new) else onSynced(new)
|
||||
if (new.status == SYNCED) onSynced(new) else onSyncing(new)
|
||||
|
|
|
@ -21,8 +21,7 @@ class HomeViewModel @Inject constructor() : ViewModel() {
|
|||
|
||||
lateinit var uiModels: Flow<UiModel>
|
||||
|
||||
private val _typedChars = ConflatedBroadcastChannel<Char>()
|
||||
private val typedChars = _typedChars.asFlow()
|
||||
lateinit var _typedChars: ConflatedBroadcastChannel<Char>
|
||||
|
||||
var initialized = false
|
||||
|
||||
|
@ -32,12 +31,19 @@ class HomeViewModel @Inject constructor() : ViewModel() {
|
|||
twig("Warning already initialized HomeViewModel. Ignoring call to initialize.")
|
||||
return
|
||||
}
|
||||
|
||||
if (::_typedChars.isInitialized) {
|
||||
_typedChars.close()
|
||||
}
|
||||
_typedChars = ConflatedBroadcastChannel()
|
||||
val typedChars = _typedChars.asFlow()
|
||||
|
||||
val zec = typedChars.scan("0") { acc, c ->
|
||||
when {
|
||||
// no-op cases
|
||||
acc == "0" && c == '0'
|
||||
|| (c == '<' && acc == "0")
|
||||
|| (c == '.' && acc.contains('.')) -> {twig("triggered: 1 acc: $acc c: $c $typedChars ")
|
||||
|| (c == '.' && acc.contains('.')) -> {twig("triggered: 1 acc: $acc c: $c")
|
||||
acc
|
||||
}
|
||||
c == '<' && acc.length <= 1 -> {twig("triggered: 2 $typedChars")
|
||||
|
@ -96,6 +102,7 @@ class HomeViewModel @Inject constructor() : ViewModel() {
|
|||
val isDownloading = status == DOWNLOADING
|
||||
val isScanning = status == SCANNING
|
||||
val isValidating = status == VALIDATING
|
||||
val isDisconnected = status == DISCONNECTED
|
||||
val downloadProgress: Int get() {
|
||||
return processorInfo.run {
|
||||
if (lastDownloadRange.isEmpty()) {
|
||||
|
|
|
@ -7,7 +7,7 @@ import com.airbnb.lottie.LottieAnimationView
|
|||
class MagicSnakeLoader(
|
||||
val lottie: LottieAnimationView,
|
||||
private val scanningStartFrame: Int = 100,
|
||||
private val scanningEndFrame: Int = 175,
|
||||
private val scanningEndFrame: Int = 187,
|
||||
val totalFrames: Int = 200
|
||||
) : ValueAnimator.AnimatorUpdateListener {
|
||||
private var isPaused: Boolean = true
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
package cash.z.ecc.android.ui.profile
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewTreeObserver
|
||||
import android.view.WindowManager
|
||||
import android.widget.Toast
|
||||
import androidx.core.view.doOnLayout
|
||||
import cash.z.ecc.android.databinding.FragmentFeedbackBinding
|
||||
import cash.z.ecc.android.di.viewmodel.viewModel
|
||||
import cash.z.ecc.android.feedback.Report
|
||||
import cash.z.ecc.android.feedback.Report.Funnel.UserFeedback
|
||||
import cash.z.ecc.android.feedback.Report.Tap.FEEDBACK_CANCEL
|
||||
import cash.z.ecc.android.feedback.Report.Tap.FEEDBACK_SUBMIT
|
||||
import cash.z.ecc.android.ui.base.BaseFragment
|
||||
|
||||
|
||||
/**
|
||||
* Fragment representing the home screen of the app. This is the screen most often seen by the user when launching the
|
||||
* application.
|
||||
*/
|
||||
class FeedbackFragment : BaseFragment<FragmentFeedbackBinding>() {
|
||||
override val screen = Report.Screen.FEEDBACK
|
||||
|
||||
override fun inflate(inflater: LayoutInflater): FragmentFeedbackBinding =
|
||||
FragmentFeedbackBinding.inflate(inflater)
|
||||
|
||||
private lateinit var ratings: Array<View>
|
||||
|
||||
// private val padder = ViewTreeObserver.OnGlobalLayoutListener {
|
||||
// Toast.makeText(mainActivity, "LAYOUT", Toast.LENGTH_SHORT).show()
|
||||
// }
|
||||
|
||||
//
|
||||
// LifeCycle
|
||||
//
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
// mainActivity!!.window.decorView.viewTreeObserver.addOnGlobalLayoutListener(padder)
|
||||
// mainActivity!!.findViewById<View>(android.R.id.content).viewTreeObserver.addOnGlobalLayoutListener(padder)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
// mainActivity!!.window.decorView.viewTreeObserver.removeOnGlobalLayoutListener(padder)
|
||||
// mainActivity!!.findViewById<View>(android.R.id.content).viewTreeObserver.removeOnGlobalLayoutListener(padder)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
with(binding) {
|
||||
backButtonHitArea.setOnClickListener(::onFeedbackCancel)
|
||||
buttonSubmit.setOnClickListener(::onFeedbackSubmit)
|
||||
|
||||
ratings = arrayOf(feedbackExp1, feedbackExp2, feedbackExp3, feedbackExp4, feedbackExp5)
|
||||
ratings.forEach {
|
||||
it.setOnClickListener(::onRatingClicked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Private API
|
||||
//
|
||||
|
||||
private fun onFeedbackSubmit(view: View) {
|
||||
Toast.makeText(mainActivity, "Thanks for the feedback!", Toast.LENGTH_LONG).show()
|
||||
tapped(FEEDBACK_SUBMIT)
|
||||
|
||||
val q1 = binding.inputQuestion1.editText?.text.toString()
|
||||
val q2 = binding.inputQuestion2.editText?.text.toString()
|
||||
val q3 = binding.inputQuestion3.editText?.text.toString()
|
||||
val rating = ratings.indexOfFirst { it.isActivated } + 1
|
||||
|
||||
mainActivity?.reportFunnel(UserFeedback.Submitted(rating, q1, q2, q3))
|
||||
|
||||
mainActivity?.navController?.navigateUp()
|
||||
}
|
||||
private fun onFeedbackCancel(view: View) {
|
||||
tapped(FEEDBACK_CANCEL)
|
||||
mainActivity?.reportFunnel(UserFeedback.Cancelled)
|
||||
mainActivity?.navController?.navigateUp()
|
||||
}
|
||||
|
||||
private fun onRatingClicked(view: View) {
|
||||
ratings.forEach { it.isActivated = false }
|
||||
view.isActivated = !view.isActivated
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
package cash.z.ecc.android.ui.profile
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
|
@ -17,6 +16,7 @@ import cash.z.ecc.android.ext.onClickNavBack
|
|||
import cash.z.ecc.android.ext.onClickNavTo
|
||||
import cash.z.ecc.android.feedback.FeedbackFile
|
||||
import cash.z.ecc.android.feedback.Report
|
||||
import cash.z.ecc.android.feedback.Report.Funnel.UserFeedback
|
||||
import cash.z.ecc.android.feedback.Report.Tap.*
|
||||
import cash.z.ecc.android.ui.base.BaseFragment
|
||||
import cash.z.wallet.sdk.ext.toAbbreviatedAddress
|
||||
|
@ -39,6 +39,11 @@ class ProfileFragment : BaseFragment<FragmentProfileBinding>() {
|
|||
super.onViewCreated(view, savedInstanceState)
|
||||
binding.hitAreaClose.onClickNavBack() { tapped(PROFILE_CLOSE) }
|
||||
binding.buttonBackup.onClickNavTo(R.id.action_nav_profile_to_nav_backup) { tapped(PROFILE_BACKUP) }
|
||||
binding.buttonFeedback.onClickNavTo(R.id.action_nav_profile_to_nav_feedback) {
|
||||
tapped(PROFILE_SEND_FEEDBACK)
|
||||
mainActivity?.reportFunnel(UserFeedback.Started)
|
||||
Unit
|
||||
}
|
||||
binding.textVersion.text = BuildConfig.VERSION_NAME
|
||||
onClick(binding.buttonLogs) {
|
||||
tapped(PROFILE_VIEW_USER_LOGS)
|
||||
|
@ -49,10 +54,6 @@ class ProfileFragment : BaseFragment<FragmentProfileBinding>() {
|
|||
onViewDevLogs()
|
||||
true
|
||||
}
|
||||
onClick(binding.buttonFeedback) {
|
||||
tapped(PROFILE_SEND_FEEDBACK)
|
||||
onSendFeedback()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
@ -95,10 +96,6 @@ class ProfileFragment : BaseFragment<FragmentProfileBinding>() {
|
|||
startActivity(Intent.createChooser(intent, "Share Log File"))
|
||||
}
|
||||
|
||||
private fun onSendFeedback() {
|
||||
mainActivity?.showSnackbar("Feedback feature coming soon!")
|
||||
}
|
||||
|
||||
private fun userLogFile(): File? {
|
||||
return mainActivity?.feedbackCoordinator?.findObserver<FeedbackFile>()?.file
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ class QrAnalyzer(val scanCallback: (qrContent: String, image: ImageProxy) -> Uni
|
|||
private fun onImageScan(result: List<FirebaseVisionBarcode>, image: ImageProxy) {
|
||||
result.firstOrNull()?.rawValue?.let {
|
||||
scanCallback(it, image)
|
||||
} ?: image.close()
|
||||
} ?: runCatching { image.close() }
|
||||
}
|
||||
|
||||
private fun onImageScanFailure(e: Exception) {
|
||||
|
|
|
@ -150,8 +150,8 @@ class SendAddressFragment : BaseFragment<FragmentSendAddressBinding>(),
|
|||
|
||||
private fun onBalanceUpdated(balance: WalletBalance) {
|
||||
binding.textLayoutAmount.helperText =
|
||||
"You have ${balance.availableZatoshi.convertZatoshiToZecString(8)} available"
|
||||
maxZatoshi = balance.availableZatoshi - ZcashSdk.MINERS_FEE_ZATOSHI
|
||||
"You have ${balance.availableZatoshi.coerceAtLeast(0L).convertZatoshiToZecString(8)} available"
|
||||
maxZatoshi = (balance.availableZatoshi - ZcashSdk.MINERS_FEE_ZATOSHI).coerceAtLeast(0L)
|
||||
}
|
||||
|
||||
override fun onPrimaryClipChanged() {
|
||||
|
|
|
@ -13,6 +13,7 @@ import cash.z.ecc.android.feedback.Report.MetricType
|
|||
import cash.z.ecc.android.feedback.Report.MetricType.*
|
||||
import cash.z.ecc.android.lockbox.LockBox
|
||||
import cash.z.ecc.android.ui.setup.WalletSetupViewModel
|
||||
import cash.z.ecc.android.ui.util.INCLUDE_MEMO_PREFIX
|
||||
import cash.z.wallet.sdk.Initializer
|
||||
import cash.z.wallet.sdk.Synchronizer
|
||||
import cash.z.wallet.sdk.entity.*
|
||||
|
@ -51,7 +52,7 @@ class SendViewModel @Inject constructor() : ViewModel() {
|
|||
var includeFromAddress: Boolean = false
|
||||
set(value) {
|
||||
require(!value || (value && !fromAddress.isNullOrEmpty())) {
|
||||
"Error: from address was empty while attempting to include it in the memo. Verify" +
|
||||
"Error: fromAddress was empty while attempting to include it in the memo. Verify" +
|
||||
" that initFromAddress() has previously been called on this viewmodel."
|
||||
}
|
||||
field = value
|
||||
|
@ -60,7 +61,7 @@ class SendViewModel @Inject constructor() : ViewModel() {
|
|||
|
||||
fun send(): Flow<PendingTransaction> {
|
||||
funnel(SendSelected)
|
||||
val memoToSend = if (includeFromAddress) "$memo\nsent from\n$fromAddress" else memo
|
||||
val memoToSend = createMemoToSend()
|
||||
val keys = initializer.deriveSpendingKeys(
|
||||
lockBox.getBytes(WalletSetupViewModel.LockBoxKey.SEED)!!
|
||||
)
|
||||
|
@ -76,6 +77,8 @@ class SendViewModel @Inject constructor() : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
fun createMemoToSend() = if (includeFromAddress) "$memo\n$INCLUDE_MEMO_PREFIX\n$fromAddress" else memo
|
||||
|
||||
private fun reportIssues(memoToSend: String) {
|
||||
if (toAddress == fromAddress) feedback.report(Issue.SelfSend)
|
||||
when {
|
||||
|
@ -98,13 +101,16 @@ class SendViewModel @Inject constructor() : ViewModel() {
|
|||
|
||||
when {
|
||||
synchronizer.validateAddress(toAddress).isNotValid -> {
|
||||
emit("Please enter a valid address")
|
||||
emit("Please enter a valid address.")
|
||||
}
|
||||
zatoshiAmount < 1 -> {
|
||||
emit("Too little! Please enter at least 1 Zatoshi.")
|
||||
emit("Please enter at least 1 Zatoshi.")
|
||||
}
|
||||
maxZatoshi != null && zatoshiAmount > maxZatoshi -> {
|
||||
emit( "Too much! Please enter no more than ${maxZatoshi.convertZatoshiToZecString(8)}")
|
||||
emit( "Please enter no more than ${maxZatoshi.convertZatoshiToZecString(8)} ZEC.")
|
||||
}
|
||||
createMemoToSend().length > ZcashSdk.MAX_MEMO_SIZE -> {
|
||||
emit( "Memo must be less than ${ZcashSdk.MAX_MEMO_SIZE} in length.")
|
||||
}
|
||||
else -> emit(null)
|
||||
}
|
||||
|
@ -191,7 +197,6 @@ class SendViewModel @Inject constructor() : ViewModel() {
|
|||
private fun Keyed<String>.toMetricIdFor(id: Long): String = "$id.$key"
|
||||
private fun String.toRelatedMetricId(): String = "$this.related"
|
||||
private fun String.toTxId(): Long = split('.').first().toLong()
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package cash.z.ecc.android.ui.util
|
||||
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
|
||||
const val INCLUDE_MEMO_PREFIX = "sent from"
|
||||
|
||||
inline fun ByteArray?.toUtf8Memo(): String {
|
||||
// TODO: make this more official but for now, this will do
|
||||
return if (this == null || this[0] >= 0xF5) "" else try {
|
||||
String(this, StandardCharsets.UTF_8).trim('\u0000')
|
||||
} catch (t: Throwable) {
|
||||
"unable to parse memo"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
if self.0[0] < 0xF5 {
|
||||
// Check if it is valid UTF8
|
||||
Some(str::from_utf8(&self.0).map(|memo| {
|
||||
// Drop trailing zeroes
|
||||
memo.trim_end_matches(char::from(0)).to_owned()
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
*/
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_activated="false" android:color="@color/text_light"/>
|
||||
<item android:state_activated="true" android:color="@color/colorPrimary" />
|
||||
</selector>
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="4dp" />
|
||||
<gradient
|
||||
android:angle="270"
|
||||
android:endColor="@color/zcashWhite_12"
|
||||
android:startColor="@color/zcashWhite_60" />
|
||||
</shape>
|
|
@ -0,0 +1,271 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<!-- Back Button -->
|
||||
<ImageView
|
||||
android:id="@+id/back_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app: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" />
|
||||
<ScrollView
|
||||
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/back_button"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:fillViewport="true">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/content_feedback"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline_content_start"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="0.1" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline_content_end"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="0.9" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_feedback_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Please rank your experience"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="22sp"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline_content_start"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_feedback_subtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="We improve and iterate with YOUR feedback"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintStart_toEndOf="@id/guideline_content_start"
|
||||
app:layout_constraintTop_toBottomOf="@id/text_feedback_title" />
|
||||
|
||||
<View
|
||||
android:id="@+id/background_buttons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="80dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@color/zcashBlack_54"
|
||||
app:layout_constraintTop_toBottomOf="@id/text_feedback_subtitle" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/feedback_exp_1"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:background="@drawable/background_circle"
|
||||
android:backgroundTint="@color/selector_feedback_button"
|
||||
android:gravity="center"
|
||||
android:text="1"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="30dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/background_buttons"
|
||||
app:layout_constraintEnd_toStartOf="@id/feedback_exp_2"
|
||||
app:layout_constraintHorizontal_chainStyle="spread_inside"
|
||||
app:layout_constraintStart_toStartOf="@id/guideline_content_start"
|
||||
app:layout_constraintTop_toTopOf="@id/background_buttons" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/feedback_exp_2"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:background="@drawable/background_circle"
|
||||
android:backgroundTint="@color/selector_feedback_button"
|
||||
android:gravity="center"
|
||||
android:text="2"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="30dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/background_buttons"
|
||||
app:layout_constraintEnd_toStartOf="@id/feedback_exp_3"
|
||||
app:layout_constraintStart_toEndOf="@id/feedback_exp_1"
|
||||
app:layout_constraintTop_toTopOf="@id/background_buttons" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/feedback_exp_3"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:background="@drawable/background_circle"
|
||||
android:backgroundTint="@color/selector_feedback_button"
|
||||
android:gravity="center"
|
||||
android:text="3"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="30dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/background_buttons"
|
||||
app:layout_constraintEnd_toStartOf="@id/feedback_exp_4"
|
||||
app:layout_constraintStart_toEndOf="@id/feedback_exp_2"
|
||||
app:layout_constraintTop_toTopOf="@id/background_buttons" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/feedback_exp_4"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:background="@drawable/background_circle"
|
||||
android:backgroundTint="@color/selector_feedback_button"
|
||||
android:gravity="center"
|
||||
android:text="4"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="30dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/background_buttons"
|
||||
app:layout_constraintEnd_toStartOf="@id/feedback_exp_5"
|
||||
app:layout_constraintStart_toEndOf="@id/feedback_exp_3"
|
||||
app:layout_constraintTop_toTopOf="@id/background_buttons" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/feedback_exp_5"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:background="@drawable/background_circle"
|
||||
android:backgroundTint="@color/selector_feedback_button"
|
||||
android:gravity="center"
|
||||
android:text="5"
|
||||
android:textColor="@color/text_dark"
|
||||
android:textSize="30dp"
|
||||
app:layout_constraintBottom_toBottomOf="@id/background_buttons"
|
||||
app:layout_constraintEnd_toEndOf="@id/guideline_content_end"
|
||||
app:layout_constraintStart_toEndOf="@id/feedback_exp_4"
|
||||
app:layout_constraintTop_toTopOf="@id/background_buttons" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_question_1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/feedback_question_1"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintStart_toStartOf="@id/guideline_content_start"
|
||||
app:layout_constraintTop_toBottomOf="@id/background_buttons" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/input_question_1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:hint="@string/feedback_hint_1"
|
||||
app:layout_constraintEnd_toEndOf="@id/guideline_content_end"
|
||||
app:layout_constraintStart_toStartOf="@id/guideline_content_start"
|
||||
app:layout_constraintTop_toBottomOf="@id/text_question_1">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="top"
|
||||
android:lines="3" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_question_2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/feedback_question_2"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintStart_toStartOf="@id/guideline_content_start"
|
||||
app:layout_constraintTop_toBottomOf="@id/input_question_1" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/input_question_2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:hint="@string/feedback_hint_2"
|
||||
app:layout_constraintEnd_toEndOf="@id/guideline_content_end"
|
||||
app:layout_constraintStart_toStartOf="@id/guideline_content_start"
|
||||
app:layout_constraintTop_toBottomOf="@id/text_question_2">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="top"
|
||||
android:lines="3" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_question_3"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/feedback_question_3"
|
||||
android:textColor="@color/text_light"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintStart_toStartOf="@id/guideline_content_start"
|
||||
app:layout_constraintTop_toBottomOf="@id/input_question_2" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/input_question_3"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:hint="@string/feedback_hint_3"
|
||||
app:layout_constraintEnd_toEndOf="@id/guideline_content_end"
|
||||
app:layout_constraintStart_toStartOf="@id/guideline_content_start"
|
||||
app:layout_constraintTop_toBottomOf="@id/text_question_3">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="top"
|
||||
android:lines="3" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button_submit"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
style="@style/Zcash.Button"
|
||||
android:gravity="center"
|
||||
android:padding="12dp"
|
||||
android:text="Send Feedback"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:textColor="#000000"
|
||||
app:layout_constraintEnd_toEndOf="@id/guideline_content_end"
|
||||
app:layout_constraintStart_toStartOf="@id/guideline_content_start"
|
||||
app:layout_constraintTop_toBottomOf="@id/input_question_3" />
|
||||
|
||||
<Space
|
||||
android:id="@+id/extra_padding_for_scrolling"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="400dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/button_submit" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -241,18 +241,6 @@
|
|||
app:layout_constraintTop_toBottomOf="@id/button_number_pad_9"
|
||||
app:layout_constraintWidth_percent="@dimen/calculator_button_width_percent" />
|
||||
|
||||
<!-- <com.google.android.material.button.MaterialButton-->
|
||||
<!-- android:id="@+id/button_send"-->
|
||||
<!-- android:layout_width="0dp"-->
|
||||
<!-- android:layout_height="0dp"-->
|
||||
<!-- style="@style/Zcash.Button"-->
|
||||
<!-- android:text=""-->
|
||||
<!-- android:enabled="false"-->
|
||||
<!-- app:layout_constraintEnd_toEndOf="@id/guide_keys"-->
|
||||
<!-- app:layout_constraintStart_toStartOf="@id/guide_keys"-->
|
||||
<!-- app:layout_constraintTop_toTopOf="@id/lottie_button_loading"-->
|
||||
<!-- app:layout_constraintBottom_toBottomOf="@id/lottie_button_loading"/>-->
|
||||
|
||||
<View
|
||||
android:id="@+id/layer_lock"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -307,7 +295,7 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:elevation="6dp"
|
||||
android:tint="@color/colorAccent"
|
||||
app:tint="@color/colorAccent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHeight_percent="0.052"
|
||||
|
@ -333,21 +321,6 @@
|
|||
app:layout_constraintWidth_percent="0.08"
|
||||
app:srcCompat="@drawable/ic_account_circle" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon_detail"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:elevation="6dp"
|
||||
android:tint="@color/colorAccent"
|
||||
app:layout_constraintBottom_toBottomOf="@id/text_detail"
|
||||
app:layout_constraintEnd_toStartOf="@id/text_detail"
|
||||
app:layout_constraintHeight_percent="0.044"
|
||||
app:layout_constraintHorizontal_chainStyle="packed"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/text_detail"
|
||||
app:layout_constraintWidth_percent="0.0887"
|
||||
app:srcCompat="@drawable/ic_receipt_24dp" />
|
||||
|
||||
<View
|
||||
android:id="@+id/hit_area_scan"
|
||||
android:layout_width="68dp"
|
||||
|
@ -376,13 +349,13 @@
|
|||
android:padding="12dp"
|
||||
android:elevation="6dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="Wallet Details"
|
||||
android:text="Wallet History"
|
||||
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:textColor="@color/colorAccent"
|
||||
android:tint="@color/colorAccent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/icon_detail"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/lottie_button_loading" />
|
||||
|
||||
<TextView
|
||||
|
|
|
@ -223,7 +223,7 @@
|
|||
android:id="@+id/group_transparent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
tools:visibility="visible"
|
||||
tools:visibility="gone"
|
||||
app:constraint_referenced_ids="sad_description, sad_icon, sad_title, sad_checkbox" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
|
@ -231,5 +231,5 @@
|
|||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:constraint_referenced_ids="clear_memo, background_memo, input_memo, check_include_address, text_info_shielded"
|
||||
tools:visibility="gone" />
|
||||
tools:visibility="visible" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -65,7 +65,6 @@
|
|||
android:id="@+id/nav_detail"
|
||||
android:name="cash.z.ecc.android.ui.detail.WalletDetailFragment"
|
||||
tools:layout="@layout/fragment_detail" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/nav_profile"
|
||||
android:name="cash.z.ecc.android.ui.profile.ProfileFragment"
|
||||
|
@ -73,7 +72,14 @@
|
|||
<action
|
||||
android:id="@+id/action_nav_profile_to_nav_backup"
|
||||
app:destination="@id/nav_backup" />
|
||||
<action
|
||||
android:id="@+id/action_nav_profile_to_nav_feedback"
|
||||
app:destination="@id/nav_feedback" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/nav_feedback"
|
||||
android:name="cash.z.ecc.android.ui.profile.FeedbackFragment"
|
||||
tools:layout="@layout/fragment_feedback" />
|
||||
|
||||
<!-- -->
|
||||
<!-- Send Navigation -->
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="buttonCornerRadius">0dp</dimen>
|
||||
|
||||
<!-- Floats -->
|
||||
|
||||
|
|
|
@ -8,7 +8,15 @@
|
|||
<!-- 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>
|
||||
|
||||
<string name="send_memo_excluded_message">Your transaction is shielded and your address is not available to the recipient</string>
|
||||
<string name="send_memo_included_message">Your transaction is shielded but your address will be sent to the recipient via the memo</string>
|
||||
|
||||
<!-- Feedback -->
|
||||
<string name="feedback_question_1">Any details you\'d like to share?</string>
|
||||
<string name="feedback_question_2">Was your balance clear?</string>
|
||||
<string name="feedback_question_3">What feature would you like to see next?</string>
|
||||
<string name="feedback_hint_1">My experience was . . .</string>
|
||||
<string name="feedback_hint_2">My balance was . . .</string>
|
||||
<string name="feedback_hint_3">I\'d like . . .</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
<item name="shapeAppearanceSmallComponent">@style/Zcash.ShapeAppearance.SmallComponent</item>
|
||||
<item name="shapeAppearanceMediumComponent">@style/Zcash.ShapeAppearance.MediumComponent</item>
|
||||
|
||||
</style>
|
||||
|
||||
<style name="ZcashTheme" parent="ZcashBaseTheme"/>
|
||||
|
@ -67,17 +68,22 @@
|
|||
|
||||
<!-- Shape Appearances -->
|
||||
|
||||
<!-- General: buttons -->
|
||||
<style name="Zcash.ShapeAppearance.SmallComponent" parent="ShapeAppearance.MaterialComponents.SmallComponent">
|
||||
<item name="cornerFamily">rounded</item>
|
||||
<item name="cornerSize">20dp</item>
|
||||
<item name="cornerSize">8dp</item>
|
||||
</style>
|
||||
|
||||
<!-- General: dialogs -->
|
||||
<style name="Zcash.ShapeAppearance.MediumComponent" parent="ShapeAppearance.MaterialComponents.MediumComponent">
|
||||
<item name="cornerFamily">rounded</item>
|
||||
<item name="cornerSize">24dp</item>
|
||||
<item name="cornerSize">12dp</item>
|
||||
</style>
|
||||
|
||||
<!-- Component -->
|
||||
<style name="Zcash.ShapeAppearance.TextInputLayout" parent="ShapeAppearance.MaterialComponents.SmallComponent">
|
||||
<item name="cornerFamily">rounded</item>
|
||||
<item name="cornerSize">8dp</item>
|
||||
<item name="cornerSize">0dp</item>
|
||||
</style>
|
||||
|
||||
<!-- Theme Overlays -->
|
||||
|
|
Loading…
Reference in New Issue