laptop froze. committing it all just in case

This commit is contained in:
Kevin Gorham 2019-06-14 13:58:10 -04:00
parent 65a3cd601a
commit 1b192ebc79
No known key found for this signature in database
GPG Key ID: ABA38E928749A19E
38 changed files with 1490 additions and 149 deletions

View File

@ -33,6 +33,7 @@ android {
dimension 'network'
applicationId 'cash.z.android.zcon1.testnet'
matchingFallbacks = ['debug', 'zcashtestnet']
resValue("string", String.join("i", "", "n", "t", "al"), "-zswag")
}
zcashmainnet {
@ -97,6 +98,7 @@ dependencies {
implementation "androidx.room:room-runtime:2.1.0-alpha06"
implementation "androidx.room:room-common:2.1.0-alpha06"
implementation 'com.google.guava:guava:27.0.1-android'
implementation 'com.mixpanel.android:mixpanel-android:5.6.3'
kapt "androidx.room:room-compiler:2.1.0-alpha06"
// Dagger

View File

@ -3,7 +3,10 @@ package cash.z.android.wallet.di.module
import android.content.SharedPreferences
import android.preference.PreferenceManager
import cash.z.android.wallet.BuildConfig
import cash.z.android.wallet.ChipBucket
import cash.z.android.wallet.InMemoryChipBucket
import cash.z.android.wallet.ZcashWalletApplication
import cash.z.android.wallet.data.StaticTransactionRepository
import cash.z.android.wallet.extention.toDbPath
import cash.z.android.wallet.sample.*
import cash.z.android.wallet.sample.SampleProperties.COMPACT_BLOCK_PORT
@ -55,15 +58,16 @@ internal object SynchronizerModule {
@Provides
@Singleton
fun provideWalletConfig(prefs: SharedPreferences): WalletConfig {
val walletName = prefs.getString(PREFS_WALLET_DISPLAY_NAME, BobWallet.displayName)
twig("FOUND WALLET DISPLAY NAME : $walletName")
return when(walletName) {
AliceWallet.displayName -> AliceWallet
BobWallet.displayName -> BobWallet // Default wallet
CarolWallet.displayName -> CarolWallet
DaveWallet.displayName -> DaveWallet
else -> WalletConfig.create(walletName)
}
return CarolWallet
// val walletName = prefs.getString(PREFS_WALLET_DISPLAY_NAME, BobWallet.displayName)
// twig("FOUND WALLET DISPLAY NAME : $walletName")
// return when(walletName) {
// AliceWallet.displayName -> AliceWallet
// BobWallet.displayName -> BobWallet // Default wallet
// CarolWallet.displayName -> CarolWallet
// DaveWallet.displayName -> DaveWallet
// else -> WalletConfig.create(walletName)
// }
}
@JvmStatic
@ -128,11 +132,13 @@ internal object SynchronizerModule {
@Singleton
fun provideRepository(
application: ZcashWalletApplication,
rustBackend: RustBackendWelding,
walletConfig: WalletConfig
): TransactionRepository {
return PollingTransactionRepository(
application,
walletConfig.dataDbName,
rustBackend,
DEFAULT_TRANSACTION_POLL_FREQUENCY_MILLIS
)
}
@ -191,4 +197,11 @@ internal object SynchronizerModule {
wallet
)
}
@JvmStatic
@Provides
@Singleton
fun provideChipBucket(): ChipBucket {
return InMemoryChipBucket()
}
}

View File

@ -6,6 +6,9 @@
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.INTERNET"/>
<!-- for analytics to know when to send information -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!--<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />-->
<!--<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />-->

View File

@ -0,0 +1,150 @@
package cash.z.android.wallet
import cash.z.android.wallet.ui.util.Analytics
import cash.z.android.wallet.ui.util.Analytics.PokerChipFunnel.*
import cash.z.android.wallet.ui.util.Analytics.trackFunnelStep
import cash.z.wallet.sdk.data.twig
import java.util.concurrent.CopyOnWriteArraySet
import cash.z.wallet.sdk.ext.ZATOSHI
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
interface ChipBucket {
fun add(chip: PokerChip)
fun remove(chip: PokerChip)
fun redeem(chip: PokerChip)
fun forEach(block: (PokerChip) -> Unit)
fun save()
fun restore(): ChipBucket
fun findChip(id: String): PokerChip?
fun valuePending(): Long
fun valueRedeemed(): Long
fun setOnBucketChangedListener(listener: OnBucketChangedListener)
fun removeOnBucketChangedListener(listener: OnBucketChangedListener)
interface OnBucketChangedListener {
fun onBucketChanged(bucket: ChipBucket)
}
}
class InMemoryChipBucket : ChipBucket {
private var listener: ChipBucket.OnBucketChangedListener? = null
private val chips = CopyOnWriteArraySet<PokerChip>()
override fun add(chip: PokerChip) {
chips.add(chip)
listener?.onBucketChanged(this)
trackFunnelStep(Collected(chip))
}
override fun remove(chip: PokerChip) {
chips.remove(chip)
listener?.onBucketChanged(this)
trackFunnelStep(Aborted(chip))
}
override fun redeem(chip: PokerChip) {
if (!chip.isRedeemed()) {
chips.remove(chip)
chips.add(chip.copy(redeemed = System.currentTimeMillis()))
listener?.onBucketChanged(this)
trackFunnelStep(Redeemed(chip, true))
}
}
override fun forEach(block: (PokerChip) -> Unit) {
if (chips.isNotEmpty()) {
for (chip in chips) {
block(chip)
}
}
}
override fun save() {}
override fun restore(): InMemoryChipBucket = this
override fun findChip(id: String) = chips.find { it.id == id }
override fun valuePending(): Long {
if(chips.isEmpty()) return -1L
return chips.fold(0L) { acc, pokerChip ->
if(pokerChip.isRedeemed()) acc else acc + pokerChip.zatoshiValue
}
}
override fun valueRedeemed(): Long {
if(chips.isEmpty()) return -1L
return chips.fold(0L) { acc, pokerChip ->
if (pokerChip.isRedeemed()) acc + pokerChip.zatoshiValue else acc
}
}
override fun setOnBucketChangedListener(listener: ChipBucket.OnBucketChangedListener) {
if (listener !== this.listener) {
this.listener = listener
listener?.onBucketChanged(this)
twig("bucket listener set to $listener")
}
}
override fun removeOnBucketChangedListener(listener: ChipBucket.OnBucketChangedListener) {
if (listener === this.listener) {
twig("removing bucket listener $listener")
this.listener = null
}
}
}
// suppress b/c we're ok w/ copies we just don't want the long constructor to be suggested in the IDE, for convenience
@Suppress("DataClassPrivateConstructor")
data class PokerChip private constructor(val id: String = "", val redeemed: Long = -1L, val created: Long = -1L) {
constructor(id: String) : this(id, created = System.currentTimeMillis())
init {
require(id.startsWith("r-") || id.startsWith("b-"))
}
val maskedId: String get() = id.split("-").let { "${it[0]}-${it[1]}-${it[2]}-**masked**" }
val chipColor: ChipColor get() = ChipColor.from(this)!! // acceptable !! because of the require contract
val zatoshiValue: Long get() = chipColor.zatoshiValue
fun isRedeemed(): Boolean {
return redeemed > 0
}
override fun hashCode(): Int {
return id.hashCode()
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
return other is PokerChip && other.id == id
}
enum class ChipColor(val zatoshiValue: Long, val idPrefix: String) {
RED(5L * ZATOSHI, "r-"),
BLACK(25L * ZATOSHI, "b-");
companion object {
fun from(chip: PokerChip): ChipColor? {
for (color in values()) {
if(chip.id.startsWith(color.idPrefix)) return color
}
return null
}
}
}
}
class PokerChipSeedProvider(val chipId: String) : ReadOnlyProperty<Any?, ByteArray> {
constructor(chip: PokerChip) : this(chip.id)
override fun getValue(thisRef: Any?, property: KProperty<*>): ByteArray {
val salt = ZcashWalletApplication.instance.getString(R.string.initial)
return "$chipId$salt".toByteArray()
}
}

View File

@ -0,0 +1,22 @@
package cash.z.android.wallet.data
import cash.z.android.wallet.ZcashWalletApplication
import cash.z.wallet.sdk.data.PollingTransactionRepository
import cash.z.wallet.sdk.data.twig
import cash.z.wallet.sdk.db.DerivedDataDb
import cash.z.wallet.sdk.jni.RustBackendWelding
import kotlinx.coroutines.CoroutineScope
// TODO: reverse this hierarchy so that Polling depends on this class and has all the overhead specific to polling, leaving this class more streamlined and efficient
class StaticTransactionRepository(dataDbName: String, rustBackend: RustBackendWelding, dbCallback: (DerivedDataDb) -> Unit = {}) :
PollingTransactionRepository(ZcashWalletApplication.instance, dataDbName, rustBackend,2000L, dbCallback) {
override fun start(parentScope: CoroutineScope) {
twig("starting repository ignored because this DB does not poll")
}
override fun stop() {
twig("stopping repository ignored because this DB does not poll")
}
}

View File

@ -38,7 +38,8 @@ import javax.inject.Singleton
// Zcon1 Fragments
Zcon1HomeFragmentModule::class,
Zcon1CartFragmentModule::class
Zcon1CartFragmentModule::class,
Zcon1FeedbackFragmentModule::class
]
)
interface ApplicationComponent : AndroidInjector<ZcashWalletApplication> {

View File

@ -1,5 +1,8 @@
package cash.z.android.wallet.extention
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import cash.z.android.wallet.ZcashWalletApplication
internal inline fun tryIgnore(block: () -> Unit) {
@ -17,4 +20,9 @@ internal inline fun String.truncate(): String {
internal inline fun String.toDbPath(): String {
return ZcashWalletApplication.instance.getDatabasePath(this).absolutePath
}
fun Context.copyToClipboard(text: CharSequence) {
val clipboard: ClipboardManager = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.primaryClip = ClipData.newPlainText("Zcon1", text)
}

View File

@ -16,17 +16,23 @@ import androidx.navigation.NavOptions
import androidx.navigation.Navigation
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.navigateUp
import cash.z.android.wallet.R
import cash.z.android.wallet.Zcon1Store
import cash.z.android.wallet.*
import cash.z.android.wallet.databinding.ActivityMainBinding
import cash.z.android.wallet.di.annotation.ActivityScope
import cash.z.android.wallet.extention.Toaster
import cash.z.android.wallet.extention.alert
import cash.z.android.wallet.extention.copyToClipboard
import cash.z.android.wallet.sample.WalletConfig
import cash.z.android.wallet.ui.fragment.ScanFragment
import cash.z.android.wallet.ui.presenter.BalancePresenter
import cash.z.android.wallet.ui.presenter.MainPresenter
import cash.z.android.wallet.ui.presenter.MainPresenterModule
import cash.z.android.wallet.ui.util.Analytics
import cash.z.android.wallet.ui.util.Analytics.PokerChipFunnel.StartSweep
import cash.z.android.wallet.ui.util.Analytics.PokerChipFunnel.Swept
import cash.z.android.wallet.ui.util.Analytics.Tap.*
import cash.z.android.wallet.ui.util.Analytics.trackAction
import cash.z.android.wallet.ui.util.Analytics.trackFunnelStep
import cash.z.android.wallet.ui.util.Broom
import cash.z.wallet.sdk.data.Synchronizer
import cash.z.wallet.sdk.data.twig
@ -51,6 +57,9 @@ class MainActivity : BaseActivity(), Animator.AnimatorListener, ScanFragment.Bar
@Inject
lateinit var broom: Broom
@Inject
lateinit var chipBucket: ChipBucket
lateinit var binding: ActivityMainBinding
lateinit var loadMessages: List<String>
@ -63,6 +72,7 @@ class MainActivity : BaseActivity(), Animator.AnimatorListener, ScanFragment.Bar
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
chipBucket.restore()
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.activity = this
initAppBar()
@ -79,6 +89,7 @@ class MainActivity : BaseActivity(), Animator.AnimatorListener, ScanFragment.Bar
override fun onResume() {
super.onResume()
chipBucket.restore()
launch {
balancePresenter.start(this, synchronizer)
mainPresenter.start()
@ -89,11 +100,13 @@ class MainActivity : BaseActivity(), Animator.AnimatorListener, ScanFragment.Bar
super.onPause()
balancePresenter.stop()
mainPresenter.stop()
chipBucket.save()
}
override fun onDestroy() {
super.onDestroy()
synchronizer.stop()
Analytics.clear()
}
private fun initAppBar() {
@ -280,6 +293,7 @@ class MainActivity : BaseActivity(), Animator.AnimatorListener, ScanFragment.Bar
}
fun onScanQr(view: View) {
trackAction(TAPPED_SCAN_QR_HOME)
supportFragmentManager.beginTransaction()
.add(R.id.camera_placeholder, ScanFragment(), "camera_fragment")
.addToBackStack("camera_fragment_scanning")
@ -287,19 +301,76 @@ class MainActivity : BaseActivity(), Animator.AnimatorListener, ScanFragment.Bar
}
fun onSendFeedback(view: View) {
Toaster.short("Feedback sent! (j/k)")
trackAction(TAPPED_GIVE_FEEDBACK)
navController.navigate(R.id.nav_feedback_fragment)
}
override fun onBarcodeScanned(value: String) {
Toaster.short(value)
playSound(Random.nextBoolean())
exitScanMode()
// Toaster.short("qr scanned")
// launch {
// val result = broom.sweep(SampleSeedProvider("testreferencebob"))
// Toaster.short("Sweep result? $result")
// }
// For now, empty happens when back is pressed
if (value.isEmpty()) return
chipBucket.findChip(value)?.let {existingChip ->
if (existingChip.isRedeemed()) {
alert("Previously Redeemed!", "We scanned this one already and the funds went to this wallet.")
} else {
alert(
title = "Still Processing",
message = "We scanned this one already and it is still processing. Would you rather wait until it finishes or abort and try again later?",
positiveButtonResId = R.string.wait,
negativeButtonResId = R.string.abort,
negativeAction = {
onAbortChip(existingChip)
}
)
}
return
}
alert(
title = "You found a token!",
message = "Would you like to magically convert this poker chip into digital money?"
) {
playSound(Random.nextBoolean())
funQuote()
launch {
val chip = PokerChip(value)
chipBucket.add(chip)
val result = sweepChip(chip)
twig("Sweep result? $result")
trackFunnelStep(Swept(chip, result))
}
}
}
private fun onAbortChip(chip: PokerChip) {
// TODO: don't remove until we're sure we can because this triggers a funnel event
chipBucket.remove(chip)
}
private suspend fun sweepChip(chip: PokerChip): String? {
trackFunnelStep(StartSweep(chip))
val provider = PokerChipSeedProvider(chip)
return broom.sweep(provider, chip.zatoshiValue)
}
val scanQuote = arrayOf(
"You're the Scrooge McDuck of Zcon1!",
"We're rich!",
"Show me the money! Oh wait, you just did. Literally.",
"Doing magic. Actual magic.",
"This is TAZmania!"
)
private fun funQuote() {
var message = scanQuote.random()
// if(message == scanQuote[0]) message = scanQuote.random() // simple way to make 0 more rare
Toaster.short(message)
}
override fun isTargetBarcode(value: String?): Boolean {
if(value == null) return false
return value.startsWith("r-") || value.startsWith("b-")
}
private fun playSound(isLarge: Boolean) {
@ -352,6 +423,23 @@ class MainActivity : BaseActivity(), Animator.AnimatorListener, ScanFragment.Bar
Toaster.short(processing.state.toString())
}
fun onFeedbackSubmit(view: View) {
trackAction(TAPPED_SUBMIT_FEEDBACK)
Toaster.short("Feedback Submitted! (j/k)")
navController.navigateUp()
}
fun onFeedbackCancel(view: View) {
Toaster.short("Feedback cancelled")
trackAction(TAPPED_CANCEL_FEEDBACK)
navController.navigateUp()
}
fun copyAddress(view: View) {
trackAction(TAPPED_COPY_ADDRESS)
Toaster.short("Address copied!")
copyToClipboard(synchronizer.getAddress())
}
}
@Module

View File

@ -1,6 +1,5 @@
package cash.z.android.wallet.ui.adapter
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -14,10 +13,10 @@ import cash.z.android.wallet.R
import cash.z.android.wallet.extention.toAppColor
import cash.z.android.wallet.extention.toRelativeTimeString
import cash.z.android.wallet.extention.truncate
import cash.z.wallet.sdk.ext.convertZatoshiToZec
import cash.z.wallet.sdk.ext.toZec
import java.text.SimpleDateFormat
import cash.z.wallet.sdk.dao.WalletTransaction
import cash.z.wallet.sdk.ext.MINERS_FEE_ZATOSHI
import cash.z.wallet.sdk.ext.convertZatoshiToZecString
import java.text.SimpleDateFormat
import java.util.*
import kotlin.math.absoluteValue
@ -41,25 +40,33 @@ class TransactionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
private val timestamp = itemView.findViewById<TextView>(R.id.text_transaction_timestamp)
private val amount = itemView.findViewById<TextView>(R.id.text_transaction_amount)
private val address = itemView.findViewById<TextView>(R.id.text_transaction_address)
private val memo = itemView.findViewById<TextView>(R.id.text_transaction_memo)
private val formatter = SimpleDateFormat("M/d h:mma", Locale.getDefault())
fun bind(tx: WalletTransaction) {
val isChip = tx.isPokerChip()
val useSend = tx.isSend && !isChip
val isHistory = icon != null
val sign = if (tx.isSend) "-" else "+"
val amountColor = if (tx.isSend) R.color.text_dark_dimmed else R.color.colorPrimary
val transactionColor = if (tx.isSend) R.color.send_associated else R.color.receive_associated
val transactionIcon = if (tx.isSend) R.drawable.ic_sent_transaction else R.drawable.ic_received_transaction
val zecAbsoluteValue = tx.value.absoluteValue.convertZatoshiToZec(6)
val toOrFrom = if (tx.isSend) "to" else "from"
val sign = if (useSend) "- " else "+ "
val amountColor = if (useSend) R.color.colorAccent else R.color.zcashPurple_accent
val transactionColor = if (useSend) R.color.send_associated else R.color.receive_associated
val transactionIcon = if (useSend || (isChip && tx.address != "Redeemed")) R.drawable.ic_sent_transaction else R.drawable.ic_received_transaction
val zecAbsoluteValue = tx.value.absoluteValue + if(tx.isSend) MINERS_FEE_ZATOSHI else 0
val toOrFrom = if (useSend) "to" else "from"
val srcOrDestination = tx.address?.truncate() ?: "shielded mystery person"
timestamp.text = if (!tx.isMined || tx.timeInSeconds == 0L) "Pending"
else (if (isHistory) formatter.format(tx.timeInSeconds * 1000) else (tx.timeInSeconds * 1000L).toRelativeTimeString())
amount.text = "$sign$zecAbsoluteValue"
timestamp.text = if ((!tx.isMined || tx.timeInSeconds == 0L) && !isChip) "Pending"
else (if (isHistory) formatter.format(tx.timeInSeconds * 1000) else (tx.timeInSeconds * 1000L).toRelativeTimeString())
amount.text = "$sign${zecAbsoluteValue.convertZatoshiToZecString(4)}"
amount.setTextColor(amountColor.toAppColor())
// maybes - and if this gets to be too much, then pass in a custom holder when constructing the adapter, instead
status?.setBackgroundColor(transactionColor.toAppColor())
address?.text = "$toOrFrom $srcOrDestination"
address?.text = if (isChip) tx.address else "$toOrFrom $srcOrDestination"
memo?.text = tx.memo
icon?.setImageResource(transactionIcon)
}
}
private fun WalletTransaction.isPokerChip(): Boolean {
return memo?.contains("Poker") == true
}

View File

@ -0,0 +1,81 @@
package cash.z.android.wallet.ui.dialog
import android.app.Dialog
import android.os.Bundle
import android.view.Gravity
import android.view.Window
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.DialogFragment
import cash.z.android.wallet.R
import cash.z.android.wallet.databinding.DialogStatusBinding
import com.google.android.material.dialog.MaterialAlertDialogBuilder
class StatusDialog(
availableBalance: Long,
syncingBalance: Long,
pendingChipBalance: Long,
summary: String
) : DialogFragment() {
init {
arguments = Bundle().apply {
putLong(ARG_BALANCE_AVAILABLE, availableBalance)
putLong(ARG_BALANCE_SYNCING, syncingBalance)
putLong(ARG_BALANCE_PENDING, pendingChipBalance)
putString(ARG_SUMMARY, summary)
}
}
lateinit var binding: DialogStatusBinding
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
binding = DataBindingUtil.inflate(activity!!.layoutInflater, R.layout.dialog_status, null, false)
val dialog = MaterialAlertDialogBuilder(context)
.setView(binding.root)
.create()
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
val lp = dialog.window.attributes
lp.gravity = Gravity.TOP
lp.y = 0
return dialog
}
//
// override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// return super.onCreateView(inflater, container, savedInstanceState)
// val window = dialog?.window
//
// // set "origin" to top left corner, so to speak
// window!!.setGravity(Gravity.TOP or Gravity.LEFT)
//
// // after that, setting values for x and y works "naturally"
// val params = window.attributes
// params.x = 0
// params.y = -200
// window.attributes = params
// }
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
// binding.statusDescription.setText(product.descriptionResId)
// binding.statusProduct.text = product.name
// binding.statusPrice.text = product.zatoshiValue.convertZatoshiToZecString(1) + " TAZ"
// binding.statusImage.setImageResource(product.imageResId)
binding.buttonOk.setOnClickListener {
dismiss()
}
}
companion object {
const val ARG_BALANCE_SYNCING = "arg_balance_syncing"
const val ARG_BALANCE_AVAILABLE = "arg_balance_available"
const val ARG_BALANCE_PENDING = "arg_balance_pending"
const val ARG_SUMMARY = "arg_summary"
}
}

View File

@ -12,6 +12,11 @@ import cash.z.android.wallet.databinding.DialogSwagBinding
import cash.z.android.wallet.ui.activity.MainActivity
import cash.z.wallet.sdk.ext.convertZatoshiToZecString
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import androidx.constraintlayout.widget.ConstraintAttribute.setAttributes
import android.R.attr.y
import android.R.attr.x
import android.view.*
class Zcon1SwagDialog(argProductId: Int, argBuyerName: String) : DialogFragment() {
@ -34,12 +39,31 @@ class Zcon1SwagDialog(argProductId: Int, argBuyerName: String) : DialogFragment(
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
binding = DataBindingUtil.inflate(activity!!.layoutInflater, R.layout.dialog_swag, null, false)
return MaterialAlertDialogBuilder(context)
val dialog = MaterialAlertDialogBuilder(context)
.setView(binding.root)
.create().also {
// it.window.setBackgroundDrawableResource(android.R.color.transparent)
}
.create()
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
val lp = dialog.window.attributes
lp.gravity = Gravity.TOP
lp.y = 0
return dialog
}
//
// override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// return super.onCreateView(inflater, container, savedInstanceState)
// val window = dialog?.window
//
// // set "origin" to top left corner, so to speak
// window!!.setGravity(Gravity.TOP or Gravity.LEFT)
//
// // after that, setting values for x and y works "naturally"
// val params = window.attributes
// params.x = 0
// params.y = -200
// window.attributes = params
// }
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)

View File

@ -20,6 +20,7 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import androidx.transition.Transition
import androidx.transition.TransitionInflater
import cash.z.android.wallet.ChipBucket
import cash.z.android.wallet.R
import cash.z.android.wallet.databinding.FragmentHomeBinding
import cash.z.android.wallet.di.annotation.FragmentScope

View File

@ -37,6 +37,11 @@ class ScanFragment : BaseFragment() {
interface BarcodeCallback {
fun onBarcodeScanned(value: String)
/**
* Any barcode that returns false from this method will be ignored and not returned through [onBarcodeScanned]
*/
fun isTargetBarcode(value: String?): Boolean
}
private val revealCamera = Runnable {
@ -84,7 +89,7 @@ class ScanFragment : BaseFragment() {
super.onCreate(savedInstanceState)
// This callback will only be called when MyFragment is at least Started.
val callback = requireActivity().onBackPressedDispatcher.addCallback(this) {
mainActivity?.onBarcodeScanned("BACK PRESSED")
mainActivity?.onBarcodeScanned("")
}
callback.isEnabled = true
}
@ -252,11 +257,13 @@ class ScanFragment : BaseFragment() {
if (results.isNotEmpty()) {
val barcode = results[0]
val value = barcode.rawValue
onScanSuccess(value!!)
// TODO: highlight the barcode
var bounds = barcode.boundingBox
var corners = barcode.cornerPoints
binding.cameraView.setBarcode(barcode)
if (barcodeCallback == null || barcodeCallback?.isTargetBarcode(value) == true) {
onScanSuccess(value!!)
}
// // TODO: highlight the barcode
// var bounds = barcode.boundingBox
// var corners = barcode.cornerPoints
// binding.cameraView.setBarcode(barcode)
}
}
} catch (t: Throwable) {

View File

@ -146,6 +146,11 @@ class SendFragment : BaseFragment(), SendPresenter.SendView, ScanFragment.Barcod
sendPresenter.inputAddressUpdated(value)
}
override fun isTargetBarcode(value: String?): Boolean {
if(value == null) return false
return value.toLowerCase().startsWith("ztest")
}
//
// Internal View Logic
@ -179,11 +184,6 @@ class SendFragment : BaseFragment(), SendPresenter.SendView, ScanFragment.Barcod
/* Init - Taps */
binding.imageSwapCurrency.setOnClickListener {
// validate the amount before we toggle (or else we lose their uncommitted change)
sendPresenter.inputHeaderUpdated(binding.textValueHeader.text.toString())
sendPresenter.inputToggleCurrency()
}
binding.buttonSendZec.setOnClickListener{
exitScanMode()
sendPresenter.inputSendPressed()
@ -201,15 +201,6 @@ class SendFragment : BaseFragment(), SendPresenter.SendView, ScanFragment.Barcod
TooltipCompat.setTooltipText(this, context.getString(R.string.send_tooltip_scan_qr))
}
binding.imageAddressShortcut?.apply {
if (BuildConfig.DEBUG) {
visibility = View.VISIBLE
TooltipCompat.setTooltipText(this, context.getString(R.string.send_tooltip_address_shortcut))
setOnClickListener(::onPasteShortcutAddress)
} else {
visibility = View.GONE
}
}
binding.dialogSendBackground.setOnClickListener { hideSendDialog() }
binding.dialogSubmitButton.setOnClickListener { onSendZec() }
binding.imageScanQr.setOnClickListener(::onScanQrCode)
@ -329,12 +320,13 @@ class SendFragment : BaseFragment(), SendPresenter.SendView, ScanFragment.Barcod
}
override fun setMemoError(message: String?) {
val validColor = R.color.zcashBlack_12.toAppColor()
val validColor = R.color.text_light.toAppColor()
val accentColor = R.color.zcashPurple_accent.toAppColor()
val errorColor = R.color.colorAccent.toAppColor()
if (message == null) {
binding.dividerMemo.setBackgroundColor(validColor)
binding.textMemoCharCount.setTextColor(validColor)
binding.textAreaMemo.setTextColor(R.color.text_dark.toAppColor())
binding.dividerMemo.setBackgroundColor(accentColor)
binding.textMemoCharCount.setTextColor(accentColor)
binding.textAreaMemo.setTextColor(validColor)
} else {
binding.dividerMemo.setBackgroundColor(errorColor)
binding.textMemoCharCount.setTextColor(errorColor)

View File

@ -28,6 +28,9 @@ class WelcomeFragment : ProgressFragment(R.id.progress_welcome) {
private lateinit var binding: FragmentZcon1WelcomeBinding
// Flag for development
private val developmentShortcut: Boolean = false
//
// Lifecycle
//
@ -54,13 +57,13 @@ class WelcomeFragment : ProgressFragment(R.id.progress_welcome) {
override fun onResume() {
super.onResume()
mainActivity?.setToolbarShown(false)
binding.lottieEccLogo.playToFrame(240/100)
binding.lottieEccLogo.playToFrame(if(developmentShortcut) 240 else 240)
binding.lottieEccLogo.speed = 1.4f
}
private suspend fun onNext() = coroutineScope {
if (mainActivity != null) {
val hasName = prefs.getString(PREFS_PSEUDONYM, null) == null
val hasName = if(developmentShortcut) true else prefs.getString(PREFS_PSEUDONYM, null) == null
val destination =
if (hasName) R.id.action_welcome_fragment_to_firstrun_fragment
else R.id.action_welcome_fragment_to_home_fragment

View File

@ -60,6 +60,12 @@ class Zcon1CartFragment : BaseFragment(), BalancePresenter.BalanceView {
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
mainActivity?.setToolbarShown(false)
mainActivity?.setNavigationShown(true)
}
override fun onResume() {
super.onResume()
mainActivity?.balancePresenter?.addBalanceView(this)
@ -75,12 +81,6 @@ class Zcon1CartFragment : BaseFragment(), BalancePresenter.BalanceView {
// Private API
//
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
mainActivity?.setToolbarShown(false)
mainActivity?.setNavigationShown(true)
}
private fun showSwagDialog(item: Zcon1Store.CartItem) {
Zcon1SwagDialog(item.id, buyerName).show(activity!!.supportFragmentManager, item.name)
}

View File

@ -0,0 +1,76 @@
package cash.z.android.wallet.ui.fragment
import android.content.SharedPreferences
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import cash.z.android.wallet.R
import cash.z.android.wallet.ZcashWalletApplication.Companion.PREFS_PSEUDONYM
import cash.z.android.wallet.Zcon1Store
import cash.z.android.wallet.databinding.FragmentZcon1FeedbackBinding
import cash.z.android.wallet.di.annotation.FragmentScope
import cash.z.android.wallet.ui.dialog.Zcon1SwagDialog
import cash.z.android.wallet.ui.presenter.BalancePresenter
import cash.z.wallet.sdk.ext.convertZatoshiToZecString
import cash.z.wallet.sdk.secure.Wallet
import dagger.Module
import dagger.android.ContributesAndroidInjector
import javax.inject.Inject
import javax.inject.Singleton
/**
* Fragment representing the home screen of the app. This is the screen most often seen by the user when launching the
* application.
*/
class Zcon1FeedbackFragment : BaseFragment() {
@Inject
lateinit var prefs: SharedPreferences
private lateinit var binding: FragmentZcon1FeedbackBinding
//
// LifeCycle
//
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return DataBindingUtil.inflate<FragmentZcon1FeedbackBinding>(
inflater, R.layout.fragment_zcon1_feedback, container, false
).let {
binding = it
it.root
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
mainActivity?.setToolbarShown(false)
mainActivity?.setNavigationShown(true)
}
//
// Private API
//
}
@Module
abstract class Zcon1FeedbackFragmentModule {
@FragmentScope
@ContributesAndroidInjector
abstract fun contributeZcon1FeedbackFragment(): Zcon1FeedbackFragment
}

View File

@ -5,24 +5,48 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import cash.z.android.wallet.ChipBucket
import cash.z.android.wallet.PokerChip
import cash.z.android.wallet.R
import cash.z.android.wallet.databinding.FragmentZcon1HomeBinding
import cash.z.android.wallet.di.annotation.FragmentScope
import cash.z.android.wallet.ui.adapter.TransactionAdapter
import cash.z.android.wallet.ui.dialog.StatusDialog
import cash.z.android.wallet.ui.presenter.BalancePresenter
import cash.z.android.wallet.ui.presenter.HomePresenterModule
import cash.z.android.wallet.ui.presenter.TransactionPresenter
import cash.z.android.wallet.ui.presenter.TransactionPresenterModule
import cash.z.wallet.sdk.dao.WalletTransaction
import cash.z.wallet.sdk.ext.MINERS_FEE_ZATOSHI
import cash.z.wallet.sdk.ext.convertZatoshiToZecString
import cash.z.wallet.sdk.secure.Wallet
import dagger.Module
import dagger.android.ContributesAndroidInjector
import kotlinx.coroutines.launch
import javax.inject.Inject
/**
* Fragment representing the home screen of the app. This is the screen most often seen by the user when launching the
* application.
*/
class Zcon1HomeFragment : BaseFragment(), BalancePresenter.BalanceView {//, SwipeRefreshLayout.OnRefreshListener, HomePresenter.HomeView {
class Zcon1HomeFragment : BaseFragment(), BalancePresenter.BalanceView, TransactionPresenter.TransactionView,
ChipBucket.OnBucketChangedListener {
//, SwipeRefreshLayout.OnRefreshListener
private lateinit var binding: FragmentZcon1HomeBinding
@Inject
lateinit var transactionPresenter: TransactionPresenter
@Inject
lateinit var chipBucket: ChipBucket
lateinit var balanceInfo: Wallet.WalletBalance
private var transactions: List<WalletTransaction> = emptyList()
//
// LifeCycle
//
@ -39,43 +63,119 @@ class Zcon1HomeFragment : BaseFragment(), BalancePresenter.BalanceView {//, Swip
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.backgroundBalanceImage.setOnClickListener {
showStatus()
}
}
private fun showStatus() {
StatusDialog(balanceInfo.available).show(activity!!.supportFragmentManager, "dialog_status")
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
mainActivity?.setToolbarShown(false)
mainActivity?.setNavigationShown(true)
mainActivity?.onNavButtonSelected(0)
binding.recyclerTransactionHistory.apply {
layoutManager = LinearLayoutManager(mainActivity, RecyclerView.VERTICAL, false)
adapter = TransactionAdapter(R.layout.item_zcon1_transaction_history)
}
}
override fun onResume() {
super.onResume()
chipBucket.setOnBucketChangedListener(this)
}
override fun onStart() {
super.onStart()
mainActivity?.balancePresenter?.addBalanceView(this)
chipBucket.setOnBucketChangedListener(this)
launch {
transactionPresenter.start()
}
}
override fun onPause() {
super.onPause()
override fun onStop() {
super.onStop()
mainActivity?.balancePresenter?.removeBalanceView(this)
chipBucket.removeOnBucketChangedListener(this)
transactionPresenter.stop()
}
//
// BalanceView Implementation
//
override fun updateBalance(balanceInfo: Wallet.WalletBalance) {
val balance = balanceInfo.available.convertZatoshiToZecString(6).split(".")
fun refreshBalance() {
val valuePending = chipBucket.valuePending()
val balance = (balanceInfo.total + valuePending).convertZatoshiToZecString(6).split(".")
binding.textIntegerDigits.text = balance[0]
binding.textFractionalDigits.text = ""
if (balance.size > 1) {
binding.textFractionalDigits.text = ".${balance[1]}"
}
}
//
// Balance listeners
//
override fun updateBalance(balanceInfo: Wallet.WalletBalance) {
this.balanceInfo = balanceInfo
refreshBalance()
}
override fun onBucketChanged(bucket: ChipBucket) {
refreshBalance()
refreshTransactions()
}
//
// TransactionView Implementation
//
override fun setTransactions(transactions: List<WalletTransaction>) {
this.transactions = transactions
refreshTransactions()
}
fun refreshTransactions() {
with (binding.recyclerTransactionHistory) {
(adapter as TransactionAdapter).submitList(addPokerChips(transactions))
postDelayed({
smoothScrollToPosition(0)
}, 100L)
}
}
private fun addPokerChips(transactions: List<WalletTransaction>): MutableList<WalletTransaction> {
val mergedTransactions = mutableListOf<WalletTransaction>()
chipBucket.forEach { mergedTransactions.add(it.toWalletTransaction()) }
mergedTransactions.addAll(transactions)
mergedTransactions.sortByDescending {
if (!it.isMined && it.isSend) Long.MAX_VALUE else it.timeInSeconds
}
return mergedTransactions
}
}
private fun PokerChip.toWalletTransaction(): WalletTransaction {
return WalletTransaction(
value = zatoshiValue - MINERS_FEE_ZATOSHI,
isSend = true,
timeInSeconds = created/1000L,
address = if (isRedeemed()) "Redeemed" else "Pending...",
memo = "Poker Chip Scanned"
)
}
@Module
abstract class Zcon1HomeFragmentModule {
@FragmentScope
@ContributesAndroidInjector(modules = [HomePresenterModule::class])
@ContributesAndroidInjector(modules = [TransactionPresenterModule::class])
abstract fun contributeZcon1HomeFragment(): Zcon1HomeFragment
}

View File

@ -0,0 +1,96 @@
package cash.z.android.wallet.ui.presenter
import cash.z.android.wallet.ui.fragment.Zcon1HomeFragment
import cash.z.android.wallet.ui.presenter.Presenter.PresenterView
import cash.z.wallet.sdk.dao.WalletTransaction
import cash.z.wallet.sdk.data.*
import dagger.Binds
import dagger.Module
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.launch
import javax.inject.Inject
class TransactionPresenter @Inject constructor(
private val view: Zcon1HomeFragment,
private val synchronizer: Synchronizer
) : Presenter {
interface TransactionView : PresenterView {
fun setTransactions(transactions: List<WalletTransaction>)
}
private var transactionJob: Job? = null
//
// LifeCycle
//
override suspend fun start() {
Twig.sprout("TransactionPresenter")
twig("TransactionPresenter starting!")
transactionJob?.cancel()
transactionJob = Job()
// transactionJob = view.launchPurchaseBinder(synchronizer.activeTransactions())
transactionJob = view.launchTransactionBinder(synchronizer.allTransactions())
}
override fun stop() {
twig("TransactionPresenter stopping!")
Twig.clip("TransactionPresenter")
transactionJob?.cancel()?.also { transactionJob = null }
}
fun CoroutineScope.launchPurchaseBinder(channel: ReceiveChannel<Map<ActiveTransaction, TransactionState>>) = launch {
twig("main purchase binder starting!")
for (new in channel) {
twig("main polled a purchase info")
bind(new)
}
twig("main purchase binder exiting!")
}
fun CoroutineScope.launchTransactionBinder(allTransactions: ReceiveChannel<List<WalletTransaction>>) = launch {
twig("transaction binder starting!")
for (walletTransactionList in allTransactions) {
twig("received ${walletTransactionList.size} transactions for presenting")
bind(walletTransactionList)
}
twig("transaction binder exiting!")
}
//
// Events
//
private fun bind(activeTransactions: Map<ActiveTransaction, TransactionState>) {
// val newestState = activeTransactions.entries.last().value
// if (newestState is TransactionState.Failure) {
// view.orderFailed(PurchaseResult.Failure(newestState.reason))
// } else {
// view.orderUpdated(PurchaseResult.Processing(newestState))
// }
}
private fun bind(transactions: List<WalletTransaction>) {
twig("binding ${transactions.size} walletTransactions")
view.setTransactions(transactions.sortedByDescending {
if (!it.isMined && it.isSend) Long.MAX_VALUE else it.timeInSeconds
})
}
sealed class PurchaseResult {
data class Processing(val state: TransactionState = TransactionState.Creating) : PurchaseResult()
data class Failure(val reason: String = "") : PurchaseResult()
}
}
@Module
abstract class TransactionPresenterModule {
@Binds
abstract fun providePresenter(transactionPresenter: TransactionPresenter): Presenter
}

View File

@ -0,0 +1,96 @@
package cash.z.android.wallet.ui.util
import android.annotation.SuppressLint
import cash.z.android.wallet.PokerChip
import cash.z.android.wallet.ZcashWalletApplication
import cash.z.wallet.sdk.data.twig
import com.mixpanel.android.mpmetrics.MixpanelAPI
object Analytics {
// TODO: Move to manifest
private const val MIXPANEL_TOKEN = "17e08c2ca8e6d1fe4f88335a2d1635cf"
@SuppressLint("StaticFieldLeak") // application context is fine to use here
private val mixpanel: MixpanelAPI = MixpanelAPI.getInstance(ZcashWalletApplication.instance, MIXPANEL_TOKEN)
fun clear() {
mixpanel.flush()
}
fun trackFunnelStep(funnel: FunnelStep) {
twig("@FunnelStep ${funnel.eventName()} : ${funnel.toProperties()}")
mixpanel.trackMap(funnel.eventName(), funnel.toProperties())
}
fun trackAction(action: Action) {
twig("@Action ${action.eventName()}")
mixpanel.trackMap(action.eventName(), mapOf("deviceTimestamp" to action.deviceTimestamp()))
}
/**
* An action is an event with no properties
*/
interface Action {
fun eventName(): String {
return this.toString()
}
fun deviceTimestamp(): Long = System.currentTimeMillis()
}
/**
* A funnel is an event with properties, typically part of a larger sequence of events.
*/
interface FunnelStep : Action {
fun toProperties(): MutableMap<String, Any>
}
/**
* Every tap in the app
*/
enum class Tap : Action {
// Feedback Screen
TAPPED_GIVE_FEEDBACK,
TAPPED_CANCEL_FEEDBACK,
TAPPED_SUBMIT_FEEDBACK,
// Received Screen
TAPPED_COPY_ADDRESS,
// Scan
TAPPED_SCAN_QR_HOME,
TAPPED_SCAN_QR_SEND,
TAPPED_SCAN_QR_ONBOARDING
}
sealed class PokerChipFunnel(val pokerChip: PokerChip, val success: Boolean = true) : FunnelStep {
class Collected(chip: PokerChip) : PokerChipFunnel(chip)
class Aborted(chip: PokerChip) : PokerChipFunnel(chip)
class StartSweep(chip: PokerChip) : PokerChipFunnel(chip)
class Swept(chip: PokerChip, val error: String?) : PokerChipFunnel(chip, error == null) {
override fun toProperties(): MutableMap<String, Any> {
return super.toProperties().apply { this["errorMessage"] = "$error" }
}
}
class Redeemed(chip: PokerChip, isSuccess: Boolean) : PokerChipFunnel(chip, isSuccess)
override fun toProperties(): MutableMap<String, Any> {
return pokerChip.toProperties().apply {
this["funnelStep"] = eventName()
this["isSuccess"] = "$success"
this["deviceTimestamp"] = deviceTimestamp()
}
}
override fun eventName(): String {
return "${PokerChipFunnel::class.simpleName}.${javaClass.simpleName}"
}
}
}
private fun PokerChip.toProperties(): MutableMap<String, Any> {
return mutableMapOf(
"PokerChip.id" to maskedId,
"PokerChip.created" to created,
"PokerChip.redeemed" to redeemed
)
}

View File

@ -1,6 +1,8 @@
package cash.z.android.wallet.ui.util
import cash.z.android.wallet.PokerChip
import cash.z.android.wallet.ZcashWalletApplication
import cash.z.android.wallet.data.StaticTransactionRepository
import cash.z.android.wallet.extention.toDbPath
import cash.z.android.wallet.extention.tryIgnore
import cash.z.android.wallet.sample.SampleProperties
@ -8,6 +10,7 @@ import cash.z.wallet.sdk.data.PollingTransactionRepository
import cash.z.wallet.sdk.data.TransactionRepository
import cash.z.wallet.sdk.data.Twig
import cash.z.wallet.sdk.data.twig
import cash.z.wallet.sdk.ext.MINERS_FEE_ZATOSHI
import cash.z.wallet.sdk.jni.RustBackendWelding
import cash.z.wallet.sdk.rpc.Service
import cash.z.wallet.sdk.secure.Wallet
@ -25,16 +28,12 @@ class Broom(
private val appWallet: Wallet
) {
val repository = PollingTransactionRepository(
ZcashWalletApplication.instance,
DATA_DB_NAME,
SampleProperties.DEFAULT_TRANSACTION_POLL_FREQUENCY_MILLIS
)
private val repository = StaticTransactionRepository(DATA_DB_NAME, rustBackend)
/**
* Gets the seed from the provider and sweeps the associated wallet
*/
suspend fun sweep(walletSeedProvider: ReadOnlyProperty<Any?, ByteArray>): Boolean = withContext(Dispatchers.IO){
suspend fun sweep(walletSeedProvider: ReadOnlyProperty<Any?, ByteArray>, amount: Long = 100_000_000L - MINERS_FEE_ZATOSHI): String? = withContext(Dispatchers.IO){
Twig.sprout("sweep")
// copy cache db
// cloneCachedBlocks() // optional?
@ -49,7 +48,6 @@ class Broom(
Twig.clip("broom-scan")
if (scanResult) {
twig("successfully scanned blocks! Ready to sweep!!!")
val amount = 100_000_000L - 10_000L
val memo = "swag shirt test"
val address = "ztestsapling1yu2zy9aanf8pjf2qvm4qmn4k6q57y2d9fcs3vz0guthxx3m2aq57qm6hkx0580m9u9635xh6ttr"
// val address = appWallet.getAddress()
@ -59,10 +57,11 @@ class Broom(
} else {
twig("failed to scan!")
}
true
null
} catch (t: Throwable) {
twig("Failed to sweep due to: ${t.message}")
false
val message = "Failed to sweep due to: ${t.message}"
twig(message)
message
} finally {
Twig.clip("sweep")
}

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="@color/text_light_dimmed"
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="M6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6L6,2zM13,9L13,3.5L18.5,9L13,9z"/>
</vector>

View File

@ -14,7 +14,7 @@
<path
android:name="_R_G_L_1_G_D_0_P_0"
android:fillAlpha="1"
android:fillColor="#009688"
android:fillColor="@color/transaction_received_color"
android:fillType="nonZero"
android:pathData=" M66 0 C66,36.45 36.45,66 0,66 C-36.45,66 -66,36.45 -66,0 C-66,-36.45 -36.45,-66 0,-66 C36.45,-66 66,-36.45 66,0c " />
</group>
@ -29,7 +29,7 @@
<path
android:name="_R_G_L_0_G_D_0_P_0"
android:fillAlpha="1"
android:fillColor="#ffffff"
android:fillColor="@color/transaction_icon_fg_color"
android:fillType="nonZero"
android:pathData=" M-17.21 17.58 C-17.21,17.58 -25.85,9.26 -25.85,9.26 C-25.85,9.26 0,-17.58 0,-17.58 C0,-17.58 25.85,9.26 25.85,9.26 C25.85,9.26 17.21,17.58 17.21,17.58 C17.21,17.58 0,-0.28 0,-0.28 C0,-0.28 -17.21,17.58 -17.21,17.58c " />
</group>

View File

@ -9,11 +9,11 @@
android:translateX="80"
android:translateY="80">
<path
android:name="_R_G_L_3_G_D_0_P_0"
android:name="_R_G_L_1_G_D_0_P_0"
android:fillAlpha="1"
android:fillColor="#f5a623"
android:fillColor="@color/transaction_send_color"
android:fillType="nonZero"
android:pathData=" M55.59 0 C55.59,30.7 30.7,55.59 0,55.59 C-30.7,55.59 -55.58,30.7 -55.58,0 C-55.58,-30.7 -30.7,-55.58 0,-55.58 C30.7,-55.58 55.59,-30.7 55.59,0c " />
android:pathData=" M66 0 C66,36.45 36.45,66 0,66 C-36.45,66 -66,36.45 -66,0 C-66,-36.45 -36.45,-66 0,-66 C36.45,-66 66,-36.45 66,0c " />
</group>
<group
android:name="_R_G_L_2_G"
@ -22,7 +22,7 @@
<path
android:name="_R_G_L_2_G_D_0_P_0"
android:fillAlpha="1"
android:fillColor="#ffffff"
android:fillColor="@color/transaction_icon_fg_color"
android:fillType="nonZero"
android:pathData=" M10.76 -10.09 C10.76,-10.09 -4.17,8.75 -4.17,8.75 C-4.17,8.75 5.67,25.78 5.67,25.78 C7.07,27.55 9.88,27.06 10.6,24.92 C10.6,24.92 26.8,-23.19 26.8,-23.19 C27.55,-25.42 25.42,-27.55 23.19,-26.8 C23.19,-26.8 -24.92,-10.6 -24.92,-10.6 C-27.06,-9.88 -27.55,-7.07 -25.77,-5.67 C-25.77,-5.67 -8.75,4.18 -8.75,4.18 C-8.75,4.18 10.09,-10.76 10.09,-10.76 C10.54,-11.12 11.12,-10.54 10.76,-10.09c " />
</group>

View File

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="22dp"
android:height="20dp"
android:viewportWidth="22"
android:viewportHeight="20">
<path
android:pathData="M0,20L3,20L3,7L0,7L0,20ZM19,13.191L16.482,17L8,17L8,7.943L10.61,3.008C11.068,3.068 11.422,3.458 11.422,3.929L11.422,9.071L19,9.071L19,13.191ZM19.423,6.071L14.422,6.071L14.422,3.929C14.422,1.763 12.657,0 10.487,0L8.808,0L5,7.199L5,20L16.726,20C17.584,20 18.383,19.566 18.862,18.841L21.558,14.763C21.847,14.326 22,13.815 22,13.287L22,8.705C22,7.253 20.844,6.071 19.423,6.071Z"
android:strokeWidth="1"
android:fillColor="@color/colorPrimary"
android:fillType="evenOdd"
android:strokeColor="#00000000"/>
</vector>

View File

@ -0,0 +1,138 @@
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/container_status_dialog"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingEnd="16dp"
android:paddingStart="16dp"
android:paddingTop="16dp">
<ScrollView
android:id="@+id/swag_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true"
app:layout_constraintEnd_toEndOf="@+id/button_purchase"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/status_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:fontFamily="sans-serif-thin"
android:text="Status"
android:textAllCaps="true"
android:textColor="@color/text_light"
android:textSize="@dimen/text_size_subtitle_1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/text_available"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Amount available:"
android:textColor="@color/text_light"
android:textSize="@dimen/text_size_body_2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/status_header" />
<TextView
android:id="@+id/text_syncing"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Amount syncing:"
android:textColor="@color/text_light"
android:textSize="@dimen/text_size_body_2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_available" />
<TextView
android:id="@+id/text_pending"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Pending chip amount:"
android:textColor="@color/text_light"
android:textSize="@dimen/text_size_body_2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_syncing" />
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="4dp"
android:background="@color/text_light_dimmed"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_pending" />
<TextView
android:id="@+id/text_status_detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Total Wallet Value:"
android:textColor="@color/text_light"
android:textSize="@dimen/text_size_body_2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/divider" />
<TextView
android:id="@+id/summary_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Summary"
android:textAllCaps="true"
android:fontFamily="sans-serif-thin"
android:textColor="@color/text_light"
android:textSize="@dimen/text_size_body_2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_status_detail" />
<TextView
android:id="@+id/text_summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="We are waiting on funds to get enough network confirmations to allow for them to be spent."
android:textColor="@color/text_light"
android:textSize="@dimen/text_size_body_2"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/summary_header" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
<Button
android:id="@+id/button_ok"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Zcash.Button.Alert"
android:text="@android:string/ok"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/swag_container" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@ -11,6 +11,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/fragment_receive_background"
android:onClick="copyAddress"
tools:context=".ui.fragment.ReceiveFragment">
<ImageView

View File

@ -137,16 +137,6 @@
app:layout_constraintTop_toTopOf="@id/transition_active_transaction_bg" />
<!-- Currency swap symbol -->
<ImageView
android:id="@+id/image_swap_currency"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="18dp"
android:tint="@color/text_light"
app:layout_constraintBottom_toBottomOf="@id/transition_active_transaction_bg"
app:layout_constraintEnd_toEndOf="@id/transition_active_transaction_bg"
app:layout_constraintTop_toTopOf="@id/transition_active_transaction_bg"
app:srcCompat="@drawable/ic_import_export_black" />
<!-- Input: Header -->
<EditText
@ -172,6 +162,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/text_light"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="@id/text_value_header"
app:layout_constraintStart_toStartOf="@id/text_value_header"
app:layout_constraintTop_toBottomOf="@id/text_value_header"
@ -223,6 +214,7 @@
android:text="$"
android:textColor="@color/text_light"
android:textSize="8dp"
android:visibility="invisible"
app:layout_constraintEnd_toStartOf="@id/text_value_subheader"
app:layout_constraintTop_toTopOf="@id/image_zec_symbol_subheader" />
@ -274,20 +266,6 @@
app:layout_constraintTop_toTopOf="@id/input_zcash_address"
app:srcCompat="@drawable/ic_qrcode_24dp" />
<ImageView
android:id="@+id/image_address_shortcut"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="20dp"
android:paddingTop="10dp"
android:paddingEnd="6dp"
android:paddingBottom="24dp"
android:tint="@color/zcashPurple_accent"
app:layout_constraintBottom_toBottomOf="@id/image_scan_qr"
app:layout_constraintEnd_toStartOf="@id/image_scan_qr"
app:layout_constraintTop_toTopOf="@id/image_scan_qr"
app:srcCompat="@drawable/ic_content_paste_black_24dp" />
<TextView
android:id="@+id/text_to_label"
android:layout_width="wrap_content"
@ -309,6 +287,7 @@
android:maxLength="@integer/memo_max_length"
android:padding="16dp"
android:textColor="@color/text_light"
tools:text="heere is a heck of a lot of text"
app:backgroundTint="#1E1331"
app:layout_constraintBottom_toTopOf="@id/button_send_zec"
app:layout_constraintEnd_toEndOf="@id/guideline_content_end"
@ -355,7 +334,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0 / 512"
android:textColor="@color/zcashGray"
android:textColor="@android:color/transparent"
app:layout_constraintBottom_toBottomOf="@id/text_area_memo"
app:layout_constraintEnd_toEndOf="@id/divider_memo"
app:layout_constraintTop_toBottomOf="@id/divider_memo" />
@ -507,9 +486,6 @@
android:layout_height="wrap_content"
app:constraint_referenced_ids="
transition_active_transaction_bg,
text_value_subheader,
text_dollar_symbol_subheader,
image_zec_symbol_subheader,
image_zec_symbol_header,
text_dollar_symbol_header,
text_amount_background,

View File

@ -0,0 +1,281 @@
<?xml version="1.0" encoding="utf-8"?>
<layout
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">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/image_bg_parallax"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0.5"
android:scaleType="centerCrop"
app:srcCompat="@drawable/bg_parrallax" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layout_cart_fragment"
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_begin="32dp" />
<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_end="32dp" />
<ImageView
android:id="@+id/image_header_icon"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginTop="64dp"
android:background="@drawable/background_circle"
android:padding="20dp"
android:scaleType="fitXY"
app:layout_constraintStart_toStartOf="@id/guideline_content_start"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_thumbup"
tools:ignore="RtlHardcoded" />
<TextView
android:id="@+id/text_feedback_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:includeFontPadding="false"
android:text="Feedback!"
android:textAllCaps="true"
android:textColor="@color/text_light"
android:textSize="@dimen/text_size_h4"
app:layout_constraintStart_toEndOf="@id/image_header_icon"
app:layout_constraintTop_toTopOf="@id/image_header_icon" />
<TextView
android:id="@+id/text_feedback_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="Rank your experience"
android:textAllCaps="true"
android:textColor="@color/text_light"
android:textSize="@dimen/text_size_body_1"
app:layout_constraintBottom_toBottomOf="@id/image_header_icon"
app:layout_constraintStart_toEndOf="@id/image_header_icon" />
<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:gravity="center"
android:text="1"
android:textColor="@color/colorPrimaryDark"
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:gravity="center"
android:text="2"
android:textColor="@color/colorPrimaryDark"
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:gravity="center"
android:text="3"
android:textColor="@color/colorPrimaryDark"
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:gravity="center"
android:text="4"
android:textColor="@color/colorPrimaryDark"
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:gravity="center"
android:text="5"
android:textColor="@color/colorPrimaryDark"
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="20dp"
android:text="@string/feedback_question_1"
android:textColor="@color/text_light"
android:textSize="@dimen/text_size_body_1"
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"
android:layout_marginTop="8dp"
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="20dp"
android:text="@string/feedback_question_2"
android:textColor="@color/text_light"
android:textSize="@dimen/text_size_body_1"
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"
android:layout_marginTop="8dp"
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="20dp"
android:text="@string/feedback_question_3"
android:textColor="@color/text_light"
android:textSize="@dimen/text_size_body_1"
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"
android:layout_marginTop="8dp"
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:singleLine="true" />
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/button_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginTop="32dp"
android:onClick="onFeedbackCancel"
android:text="@string/cancel"
android:textColor="@color/text_light"
app:layout_constraintEnd_toStartOf="@id/button_submit"
app:layout_constraintTop_toBottomOf="@id/input_question_3" />
<Button
android:id="@+id/button_submit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onFeedbackSubmit"
android:text="Submit"
android:textColor="@color/text_light"
app:layout_constraintEnd_toEndOf="@id/guideline_content_end"
app:layout_constraintTop_toTopOf="@id/button_cancel" />
<Space
android:id="@+id/extra_padding_for_scrolling"
android:layout_width="match_parent"
android:layout_height="200dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/button_submit"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</FrameLayout>
</layout>

View File

@ -46,11 +46,13 @@
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager_firstrun"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_width="0dp"
android:layout_height="0dp"
android:overScrollMode="never"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/progress_firstrun"/>
<ProgressBar
android:id="@+id/progress_firstrun"

View File

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<layout
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">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/content_home"
@ -36,28 +38,56 @@
app:srcCompat="@drawable/ic_bg_path" />
<ImageView
android:id="@+id/background_balance_image"
android:layout_width="match_parent"
android:layout_height="0dp"
android:alpha="0.6"
android:scaleType="fitXY"
app:layout_constraintHeight_default="percent"
app:layout_constraintHeight_percent="0.38"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:alpha="0.6"
app:srcCompat="@drawable/bg_mountains" />
<TextView
android:id="@+id/text_integer_digits"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="25"
android:includeFontPadding="false"
android:text="25"
android:textColor="@color/text_light"
android:textSize="@dimen/text_size_h1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/background_buttons"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/background_buttons"/>
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- Status -->
<TextView
android:id="@+id/text_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="status: syncing"
android:paddingBottom="3dp"
android:includeFontPadding="false"
android:fontFamily="sans-serif-thin"
android:textSize="@dimen/text_size_subtitle_1"
android:textColor="@color/text_light"
app:layout_constraintBottom_toTopOf="@id/background_buttons"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_fractional_digits" />
<View
android:id="@+id/indicator_status"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginEnd="8dp"
android:background="@drawable/background_circle"
android:backgroundTint="@color/zcashRed"
app:layout_constraintTop_toTopOf="@id/text_status"
app:layout_constraintBottom_toBottomOf="@id/text_status"
app:layout_constraintEnd_toStartOf="@id/text_status"/>
<TextView
android:id="@+id/text_fractional_digits"
@ -73,16 +103,16 @@
android:id="@+id/text_taz"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TA"
android:fontFamily="sans-serif-thin"
android:textColor="@color/colorAccent"
android:includeFontPadding="false"
android:textSize="24dp"
android:alpha="0.5"
android:fontFamily="sans-serif-thin"
android:includeFontPadding="false"
android:text="TA"
android:textColor="@color/colorAccent"
android:textSize="24dp"
android:textStyle="bold"
app:layout_constraintTop_toTopOf="@id/image_zec_symbol"
app:layout_constraintBottom_toBottomOf="@id/image_zec_symbol"
app:layout_constraintEnd_toStartOf="@id/image_zec_symbol"/>
app:layout_constraintEnd_toStartOf="@id/image_zec_symbol"
app:layout_constraintTop_toTopOf="@id/image_zec_symbol" />
<!-- Zec symbol -->
<ImageView
@ -91,9 +121,10 @@
android:layout_height="24dp"
android:layout_marginTop="18dp"
android:tint="@color/colorAccent"
app:layout_constraintTop_toTopOf="@id/text_integer_digits"
app:layout_constraintEnd_toStartOf="@id/text_integer_digits"
app:layout_constraintTop_toTopOf="@id/text_integer_digits"
app:srcCompat="@drawable/ic_zec_symbol" />
<View
android:id="@+id/background_buttons"
android:layout_width="match_parent"
@ -129,5 +160,21 @@
app:layout_constraintStart_toEndOf="@id/button_scan"
app:layout_constraintTop_toTopOf="@id/background_buttons" />
<!-- Transactions -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_transaction_history"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginBottom="16dp"
android:clipToPadding="false"
android:paddingBottom="120dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/background_buttons"
tools:itemCount="15"
tools:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_zcon1_transaction_history"
tools:orientation="vertical" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>

View File

@ -6,7 +6,8 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/container_welcome"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:background="@drawable/bg_parrallax">
<!-- Bottom Navigation (Custom) -->
@ -14,6 +15,7 @@
android:id="@+id/lottie_ecc_logo"
android:layout_width="0dp"
android:layout_height="0dp"
android:padding="72dp"
android:scaleType="centerInside"
app:layout_constraintDimensionRatio="H,1:1"
app:layout_constraintBottom_toBottomOf="parent"

View File

@ -0,0 +1,91 @@
<?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:id="@+id/container_transaction"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="1dp"
android:background="#F0291441"
android:elevation="1dp"
android:padding="16dp"
tools:ignore="RtlSymmetry">
<TextView
android:id="@+id/text_transaction_amount"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="end"
android:textColor="@color/zcashPurple_accent"
android:textSize="@dimen/text_size_h6"
app:layout_constraintBottom_toTopOf="@id/text_transaction_timestamp"
app:layout_constraintEnd_toEndOf="@id/text_transaction_timestamp"
app:layout_constraintStart_toStartOf="@id/text_transaction_timestamp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="+ 4.244" />
<TextView
android:id="@+id/text_transaction_timestamp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-thin"
android:gravity="end"
android:maxEms="6"
android:minEms="6"
android:textColor="@color/text_light"
android:textSize="@dimen/text_size_body_2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_transaction_amount"
tools:text="8/23 3:24pm" />
<ImageView
android:id="@+id/image_transaction_type"
android:layout_width="62dp"
android:layout_height="62dp"
android:layout_marginStart="8dp"
android:scaleType="fitXY"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@id/text_transaction_timestamp"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_received_transaction" />
<TextView
android:id="@+id/text_transaction_address"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:fontFamily="sans-serif-thin"
android:maxLines="1"
android:paddingStart="3dp"
android:textColor="@color/text_light"
android:textSize="@dimen/text_size_body_2"
app:layout_constraintBottom_toTopOf="@id/text_transaction_memo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/image_transaction_type"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="ztestsapling1yu2zy9aanf8pjf2qvm4qmn4k6q57y2d" />
<TextView
android:id="@+id/text_transaction_memo"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/ic_memo"
android:drawablePadding="4dp"
android:ellipsize="end"
android:fontFamily="sans-serif-thin"
android:gravity="center_vertical"
android:maxLines="1"
android:textColor="@color/text_light_dimmed"
android:textSize="@dimen/text_size_body_2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@id/text_transaction_address"
app:layout_constraintTop_toBottomOf="@id/text_transaction_address"
tools:text="ECC Swag Journal" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -109,8 +109,8 @@
android:label="@string/destination_title_settings"
tools:layout="@layout/fragment_placeholder" />
<fragment
android:id="@+id/nav_scan_fragment"
android:name="cash.z.android.wallet.ui.fragment.Zcon1ScanFragment"
tools:layout="@layout/fragment_zcon1_scan" />
android:id="@+id/nav_feedback_fragment"
android:name="cash.z.android.wallet.ui.fragment.Zcon1FeedbackFragment"
tools:layout="@layout/fragment_zcon1_feedback" />
</navigation>

File diff suppressed because one or more lines are too long

View File

@ -57,7 +57,7 @@
<!-- association -->
<color name="send_associated">@color/colorAccent</color>
<color name="receive_associated">@color/colorPrimary</color> <!-- design decision: when you get money it should be associated with green -->
<color name="receive_associated">@color/zcashPurple_accent</color> <!-- design decision: when you get money it should be associated with green -->
<color name="request_associated">@color/zcashBlue</color>
<!-- icons -->
@ -68,6 +68,7 @@
<color name="fab_closed_color">@color/colorPrimary</color>
<color name="fab_open_color">@color/colorPrimaryDark</color>
<!-- text -->
<color name="text_light">@color/zcashWhite</color>
<color name="text_light_dimmed">@color/zcashWhite_50</color>
@ -102,4 +103,9 @@
<color name="receive_shield">@color/zcashPurple_dark</color>
<color name="receive_segments">@color/colorAccent</color>
<!-- Zcon1 -->
<color name="transaction_icon_fg_color">@color/zcashBlack_54</color>
<color name="transaction_send_color">#FFE082</color>
<color name="transaction_received_color">#80CBC4</color>
</resources>

View File

@ -13,6 +13,8 @@
<string name="ignore">ignore</string>
<string name="details">details</string>
<string name="cancel">cancel</string>
<string name="wait">Wait</string>
<string name="abort">Abort</string>
<string name="cancelled">cancelled</string>
<string name="ok_allcaps">OK</string>
<string name="zec_abbreviation">ZECC</string>
@ -108,6 +110,14 @@
<string name="swag_tee_description">The Zcash Company has rebranded as The Electric Coin Company to help clear up confusion between the coin and the company. Now Zcash Co. is just ECC and you can be the first to sport the new logo :)\n\nLimited quantity of sizes.</string>
<string name="swag_pad_description">Take notes like a pro with this ECC-branded moleskin notebook. There are only a few of these, so beg your friends for their extra TAZ and buy one quickly!\n\nVery limited supplies.</string>
<!-- Feedback -->
<string name="feedback_question_1">Any details you\'d like to share?</string>
<string name="feedback_question_2">How do you normally use Zcash?</string>
<string name="feedback_question_3">Interested in being a beta tester?</string>
<string name="feedback_hint_1">My experience was . . .</string>
<string name="feedback_hint_2">I use Zcash . . .</string>
<string name="feedback_hint_3">email (optional)</string>
<string-array name="demo_user_names">
<item>Alice</item>