Implemented the Restore feature.

This commit is contained in:
Kevin Gorham 2020-01-31 11:31:36 -05:00
parent 61ec3bed66
commit 899e48b9f3
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
18 changed files with 4844 additions and 3 deletions

View File

@ -159,6 +159,10 @@ dependencies {
// Misc.
implementation 'com.airbnb.android:lottie:3.1.0'
implementation 'com.facebook.stetho:stetho:1.5.1'
implementation 'com.github.gmale:chips-input-layout:4cd87089930fc16176f9fad1362b48a392449e23'
implementation "com.github.gmale:nachos:8a71c6a"
// Tests

View File

@ -37,7 +37,7 @@ class ZcashWalletApp : Application(), CameraXConfig.Provider {
override fun onCreate() {
Thread.setDefaultUncaughtExceptionHandler(ExceptionReporter(Thread.getDefaultUncaughtExceptionHandler()))
Twig.plant(SilentTwig())
Twig.plant(TroubleshootingTwig())
creationTime = System.currentTimeMillis()
instance = this
// Setup handler for uncaught exceptions.

View File

@ -4,7 +4,6 @@ package cash.z.ecc.android.di.module
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import cash.z.ecc.android.di.annotation.ActivityScope
import cash.z.ecc.android.di.annotation.SynchronizerScope
import cash.z.ecc.android.di.annotation.ViewModelKey
import cash.z.ecc.android.di.viewmodel.ViewModelFactory
import cash.z.ecc.android.ui.setup.WalletSetupViewModel
@ -26,6 +25,7 @@ abstract class ViewModelsActivityModule {
@ViewModelKey(WalletSetupViewModel::class)
abstract fun bindWalletSetupViewModel(implementation: WalletSetupViewModel): ViewModel
/**
* Factory for view models that are created until before the Synchronizer exists. This is a
* little tricky because we cannot make them all in one place or else they won't be available

View File

@ -13,6 +13,13 @@ inline fun <reified VM : ViewModel> BaseFragment<*>.viewModel() = object : Lazy<
?: ViewModelProvider(this@viewModel, scopedFactory<VM>())[VM::class.java]
}
/**
* Create a view model that is scoped to the lifecycle of the activity.
*
* @param isSynchronizerScope true when this view model depends on the Synchronizer. False when this
* viewModel needs to be created before the synchronizer or otherwise has no dependency on it being
* available for use.
*/
inline fun <reified VM : ViewModel> BaseFragment<*>.activityViewModel(isSynchronizerScope: Boolean = true) = object : Lazy<VM> {
val cached: VM? = null
override fun isInitialized(): Boolean = cached != null

View File

@ -1,5 +1,6 @@
package cash.z.ecc.android.ext
import android.content.res.Resources
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.annotation.IntegerRes
@ -28,3 +29,9 @@ internal inline fun @receiver:StringRes Int.toAppString(): String {
internal inline fun @receiver:IntegerRes Int.toAppInt(): Int {
return ZcashWalletApp.instance.resources.getInteger(this)}
fun Float.toPx() = this * Resources.getSystem().displayMetrics.density
fun Int.toPx() = (this * Resources.getSystem().displayMetrics.density + 0.5f).toInt()
fun Int.toDp() = (this / Resources.getSystem().displayMetrics.density + 0.5f).toInt()

View File

@ -94,7 +94,7 @@ class LandingFragment : BaseFragment<FragmentLandingBinding>() {
}
private fun onRestoreWallet() {
Toast.makeText(activity, "Coming soon!", Toast.LENGTH_SHORT).show()
mainActivity?.navController?.navigate(R.id.action_nav_landing_to_nav_restore)
}
// AKA import wallet

View File

@ -0,0 +1,313 @@
package cash.z.ecc.android.ui.setup
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.os.SystemClock
import android.text.Editable
import android.text.InputType
import android.view.*
import android.view.MotionEvent.ACTION_DOWN
import android.view.MotionEvent.ACTION_UP
import android.view.inputmethod.InputMethodManager
import android.widget.TextView
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView
import cash.z.ecc.android.R
import cash.z.ecc.android.databinding.FragmentRestoreBinding
import cash.z.ecc.android.di.viewmodel.activityViewModel
import cash.z.ecc.android.ext.goneIf
import cash.z.ecc.android.ext.toAppColor
import cash.z.ecc.android.ext.toPx
import cash.z.ecc.android.ui.base.BaseFragment
import cash.z.wallet.sdk.ext.ZcashSdk
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.hootsuite.nachos.ChipConfiguration
import com.hootsuite.nachos.chip.ChipCreator
import com.hootsuite.nachos.chip.ChipSpan
import com.hootsuite.nachos.tokenizer.SpanChipTokenizer
import com.tylersuehr.chips.Chip
import com.tylersuehr.chips.ChipsAdapter
import com.tylersuehr.chips.SeedWordAdapter
import kotlinx.coroutines.launch
class RestoreFragment : BaseFragment<FragmentRestoreBinding>(), View.OnKeyListener {
private val walletSetup: WalletSetupViewModel by activityViewModel(false)
private lateinit var seedWordRecycler: RecyclerView
private var seedWordAdapter: SeedWordAdapter? = null
override fun inflate(inflater: LayoutInflater): FragmentRestoreBinding =
FragmentRestoreBinding.inflate(inflater)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
seedWordRecycler = binding.chipsInput.findViewById<RecyclerView>(R.id.chips_recycler)
seedWordAdapter = SeedWordAdapter(seedWordRecycler.adapter as ChipsAdapter).onDataSetChanged {
onChipsModified()
}.also { onChipsModified() }
seedWordRecycler.adapter = seedWordAdapter
binding.chipsInput.apply {
setFilterableChipList(getChips())
setDelimiter("[ ;,]", true)
}
binding.buttonDone.setOnClickListener {
onDone()
}
binding.buttonSuccess.setOnClickListener {
onEnterWallet()
}
//
//
// seedWordAdapter!!.editText.setOnKeyListener(this)
binding.textTitle.setOnClickListener {
seedWordAdapter!!.editText.inputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS or InputType.TYPE_CLASS_NUMBER
}
binding.textSubtitle.setOnClickListener {
seedWordAdapter!!.editText.inputType = InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD or InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
mainActivity?.onFragmentBackPressed(this) {
if (seedWordAdapter == null || seedWordAdapter?.itemCount == 1) {
onExit()
} else {
MaterialAlertDialogBuilder(activity)
.setMessage("Are you sure? For security, the words that you have entered will be cleared!")
.setTitle("Abort?")
.setPositiveButton("Stay") { dialog, _ ->
dialog.dismiss()
}
.setNegativeButton("Exit") { dialog, _ ->
dialog.dismiss()
onExit()
}
.show()
}
}
}
override fun onResume() {
super.onResume()
// Require one less tap to enter the seed words
touchScreenForUser()
}
private fun onExit() {
hideAutoCompleteWords()
setKeyboardShown(false)
mainActivity?.navController?.popBackStack()
}
private fun onEnterWallet() {
mainActivity?.navController?.navigate(R.id.action_nav_restore_to_nav_home)
}
private fun onDone() {
setKeyboardShown(false)
val seedPhrase = binding.chipsInput.selectedChips.joinToString(" ") {
it.title
}
var birthday = binding.root.findViewById<TextView>(R.id.input_birthdate).text.toString()
.let { birthdateString ->
if (birthdateString.isNullOrEmpty()) ZcashSdk.SAPLING_ACTIVATION_HEIGHT else birthdateString.toInt()
}.coerceAtLeast(ZcashSdk.SAPLING_ACTIVATION_HEIGHT)
importWallet(seedPhrase, birthday)
}
private fun importWallet(seedPhrase: String, birthday: Int) {
setKeyboardShown(false)
mainActivity?.apply {
lifecycleScope.launch {
mainActivity?.startSync(walletSetup.importWallet(seedPhrase, birthday))
}
playSound("sound_receive_small.mp3")
vibrateSuccess()
}
binding.groupDone.visibility = View.GONE
binding.groupStart.visibility = View.GONE
binding.groupSuccess.visibility = View.VISIBLE
}
private fun onChipsModified() {
seedWordAdapter?.editText?.apply {
postDelayed({
requestFocus()
isCursorVisible = false
},40L)
}
setDoneEnabled()
}
private fun setDoneEnabled() {
val count = seedWordAdapter?.itemCount ?: 0
binding.groupDone.goneIf(count <= 24)
}
private fun setKeyboardShown(isShown: Boolean) {
if (isShown) {
mainActivity?.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
} else {
(requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).hideSoftInputFromWindow(view!!.windowToken, 0);
}
}
private fun hideAutoCompleteWords() {
seedWordAdapter?.editText?.setText("")
}
private fun getChips(): List<Chip> {
return resources.getStringArray(R.array.word_list).map {
SeedWordChip(it)
}
}
private fun touchScreenForUser() {
seedWordAdapter?.editText?.apply {
postDelayed({
seedWordAdapter?.editText?.inputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
dispatchTouchEvent(motionEvent(ACTION_DOWN))
dispatchTouchEvent(motionEvent(ACTION_UP))
}, 100L)
}
}
private fun motionEvent(action: Int) = SystemClock.uptimeMillis().let { now ->
MotionEvent.obtain(now, now, action, 0f, 0f, 0)
}
override fun onKey(v: View?, keyCode: Int, event: KeyEvent?): Boolean {
return false
}
}
class SeedWordTokenizer(context: Context) : SpanChipTokenizer<ChipSpan>(context, MyChipSpanChipCreator(), ChipSpan::class.java) {
override fun applyConfiguration(text: Editable, chipConfiguration: ChipConfiguration) {
mChipConfiguration = chipConfiguration
val allChips = findAllChips(0, text.length, text)
allChips.forEachIndexed { i, chip ->
val chipStart = findChipStart(chip, text)
deleteChip(chip, text)
val newChip = (mChipCreator as MyChipSpanChipCreator).createChip(mContext, chip, i)
text.insert(chipStart, terminateToken(newChip))
}
}
}
class MyChipSpanChipCreator : ChipCreator<ChipSpan> {
override fun createChip(context: Context, text: CharSequence, data: Any?): ChipSpan {
return MyChipSpan(context, text, ContextCompat.getDrawable(context, R.mipmap.ic_launcher), data)
}
fun createChip(context: Context, existingChip: ChipSpan, data: Any?): MyChipSpan {
return MyChipSpan(context, existingChip, data)
}
override fun createChip(context: Context, existingChip: ChipSpan): ChipSpan {
throw IllegalAccessException("Provide data when creating a chip")
}
override fun configureChip(chip: ChipSpan, chipConfiguration: ChipConfiguration) {
val chipHorizontalSpacing = chipConfiguration.chipHorizontalSpacing
val chipBackground = chipConfiguration.chipBackground
val chipCornerRadius = chipConfiguration.chipCornerRadius
val chipTextColor = chipConfiguration.chipTextColor
val chipTextSize = chipConfiguration.chipTextSize
val chipHeight = chipConfiguration.chipHeight
val chipVerticalSpacing = chipConfiguration.chipVerticalSpacing
val maxAvailableWidth = chipConfiguration.maxAvailableWidth
if (chipHorizontalSpacing != -1) {
chip.setLeftMargin(chipHorizontalSpacing / 2)
chip.setRightMargin(chipHorizontalSpacing / 2)
}
if (chipBackground != null) {
chip.setBackgroundColor(chipBackground)
}
if (chipCornerRadius != -1) {
chip.setCornerRadius(chipCornerRadius)
}
if (chipTextColor != Color.TRANSPARENT) {
chip.setTextColor(chipTextColor)
}
if (chipTextSize != -1) {
chip.setTextSize(chipTextSize)
}
if (chipHeight != -1) {
chip.setChipHeight(chipHeight)
}
if (chipVerticalSpacing != -1) {
chip.setChipVerticalSpacing(chipVerticalSpacing)
}
if (maxAvailableWidth != -1) {
chip.setMaxAvailableWidth(maxAvailableWidth)
}
chip.setShowIconOnLeft(true)
}
}
class MyChipSpan : ChipSpan {
val index: Int
constructor(context: Context, text: CharSequence, drawable: Drawable?, data: Any?)
: super(context, text, drawable, data) {
index = data as? Int ?: 0
}
constructor(context: Context, chip: ChipSpan, data: Any?)
: super(context, chip) {
index = data as? Int ?: 0
}
override fun drawBackground(canvas: Canvas, x: Float, top: Int, bottom: Int, paint: Paint) {
val rect = RectF(x, top.toFloat(), x + mChipWidth, bottom.toFloat())
val cornerRadius = 4.0f.toPx()
paint.color = R.color.background_banner.toAppColor()
canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint)
paint.color = R.color.background_banner_stroke.toAppColor()
paint.style = Paint.Style.STROKE
paint.strokeJoin = Paint.Join.ROUND
paint.strokeMiter = 10.0f
paint.strokeWidth = 1.5f.toPx()
canvas.drawRoundRect(rect, cornerRadius, cornerRadius, paint)
paint.style = Paint.Style.FILL
paint.color = mTextColor
}
}
class SeedWordChip(val word: String, var index: Int = -1) : Chip() {
override fun getSubtitle(): String? = null//"subtitle for $word"
override fun getAvatarDrawable(): Drawable? = null
override fun getId() = index
override fun getTitle() = word
override fun getAvatarUri() = null
}

View File

@ -0,0 +1,74 @@
package com.tylersuehr.chips
import android.content.Context
import android.text.TextUtils
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import cash.z.ecc.android.R
import cash.z.ecc.android.ext.toAppColor
class SeedWordAdapter : ChipsAdapter {
constructor(existingAdapter: ChipsAdapter) : super(existingAdapter.mDataSource, existingAdapter.mEditText, existingAdapter.mOptions)
val editText = mEditText
private var onDataSetChangedListener: (() -> Unit)? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == CHIP) SeedWordHolder(SeedWordChipView(parent.context))
else object : RecyclerView.ViewHolder(mEditText) {}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (getItemViewType(position) == CHIP) { // Chips
// Display the chip information on the chip view
(holder as SeedWordHolder).seedChipView.bind(mDataSource.getSelectedChip(position), position);
} else {
val size = mDataSource.selectedChips.size
mEditText.hint = if (size < 3) {
mEditText.isEnabled = true
mEditText.setHintTextColor(R.color.text_light_dimmed.toAppColor())
val ordinal = when(size) {2 -> "3rd"; 1 -> "2nd"; else -> "1st"}
"Enter $ordinal seed word"
} else if(size >= 24) {
mEditText.setHintTextColor(R.color.zcashGreen.toAppColor())
mEditText.isEnabled = false
"done"
} else {
mEditText.isEnabled = true
mEditText.setHintTextColor(R.color.zcashYellow.toAppColor())
"${size + 1}"
}
}
onDataSetChangedListener?.invoke()
}
fun onDataSetChanged(block: () -> Unit): SeedWordAdapter {
onDataSetChangedListener = block
return this
}
override fun onKeyboardActionDone(text: String?) {
if (TextUtils.isEmpty(text)) return
if (mDataSource.originalChips.firstOrNull { it.title == text } != null) {
mEditText.setText("");
mDataSource.addSelectedChip(DefaultCustomChip(text))
}
}
private inner class SeedWordHolder(chipView: SeedWordChipView) : ChipsAdapter.ChipHolder(chipView) {
val seedChipView = super.chipView as SeedWordChipView
}
private inner class SeedWordChipView(context: Context) : ChipView(context) {
private val indexView: TextView = findViewById(R.id.chip_index)
fun bind(chip: Chip, index: Int) {
super.inflateFromChip(chip)
indexView.text = (index + 1).toString()
}
}
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="3dp" />
<stroke android:width="1dp" android:color="#282828"/>
<solid android:color="@color/background_banner"/>
</shape>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z"/>
</vector>

View File

@ -0,0 +1,65 @@
<?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"
android:layout_width="wrap_content"
android:layout_height="@dimen/chip_height"
android:background="@drawable/bg_chip_view"
android:clickable="true">
<com.tylersuehr.chips.CircleImageView
android:id="@+id/avatar"
android:layout_width="@dimen/chip_height"
android:layout_height="@dimen/chip_height"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/avatar" />
<TextView
android:id="@+id/chip_index"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginTop="2dp"
android:text="12"
android:textSize="12dp"
android:fontFamily="@font/inconsolata"
android:textColor="@color/text_light_dimmed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="4dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="4dp"
android:layout_marginStart="8dp"
android:textColor="@color/text_light_dimmed"
android:textSize="@dimen/chip_label_text_size"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/chip_index"
app:layout_constraintRight_toLeftOf="@+id/button_delete"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="artwork" />
<ImageButton
android:id="@+id/button_delete"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:alpha=".54"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/chip_delete_icon_20dp"
android:tint="@color/zcashWhite_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:paddingTop="16dp"
android:clickable="true">
<com.tylersuehr.chips.CircleImageView
android:id="@+id/image"
android:layout_width="40dp"
android:layout_height="40dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:visibility="gone"
tools:src="@drawable/avatar"/>
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:textSize="18dp"
android:includeFontPadding="false"
android:textColor="@color/text_light"
app:layout_constraintBottom_toTopOf="@+id/subtitle"
app:layout_constraintLeft_toRightOf="@+id/image"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Title"/>
<TextView
android:id="@+id/subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:alpha=".56"
android:textSize="@dimen/chip_label_text_size"
android:textColor="#212121"
android:visibility="gone"
app:layout_constraintTop_toBottomOf="@+id/title"
app:layout_constraintLeft_toRightOf="@+id/image"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
tools:text="Subtitle"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,187 @@
<?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="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:background="@drawable/background_home">
<TextView
android:id="@+id/text_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="start"
android:paddingStart="32dp"
android:paddingTop="32dp"
android:text="Restoring from a backup"
android:textColor="@color/text_light"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/text_subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="start"
android:paddingBottom="32dp"
android:paddingStart="32dp"
android:paddingTop="18dp"
android:text="You will need to enter all 24 seed words.\nDon't worry, we will find them as you type."
android:textColor="@color/text_light_dimmed"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_title" />
<com.tylersuehr.chips.ChipsInputLayout
android:id="@+id/chips_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/zcashBlack_54"
android:hint="Enter the 1st seed word..."
android:paddingEnd="16dp"
android:paddingStart="16dp"
android:scrollbarStyle="outsideOverlay"
android:textColor="@color/text_light"
android:textColorHint="#757575"
app:hideKeyboardOnChipClick="false"
app:allowCustomChips="false"
app:chip_showDelete="false"
app:chip_showDetails="true"
app:chip_textColor="@color/text_light_dimmed"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_subtitle" />
<View
android:id="@+id/divider_top"
android:layout_width="match_parent"
android:layout_height="1px"
android:background="@color/text_light_dimmed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/chips_input" />
<View
android:id="@+id/divider_bottom"
android:layout_width="match_parent"
android:layout_height="1px"
android:background="@color/text_light_dimmed"
app:layout_constraintBottom_toBottomOf="@id/chips_input"
app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/text_layout_birthdate"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="Enter wallet birthday height (recommended)"
android:theme="@style/Zcash.Overlay.TextInputLayout"
app:helperText="e.g. 419,200. This determines where to start scanning for transactions. Leave it blank to scan from the beginning, which takes a while."
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/divider_bottom"
app:layout_constraintWidth_percent="0.84">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/input_birthdate"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:imeOptions="actionDone"
android:inputType="number"
android:maxLength="6"
android:singleLine="true"
android:textColor="@color/text_light"
android:textColorHint="@color/text_light_dimmed" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/button_done"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="32dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="Import"
android:textColor="@color/text_dark"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_layout_birthdate" />
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/lottie_success"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="W,1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.33333"
app:layout_constraintWidth_percent="0.4053398058"
app:lottie_autoPlay="true"
app:lottie_loop="false"
app:lottie_rawRes="@raw/lottie_success" />
<!-- <ImageView-->
<!-- android:id="@+id/icon_success"-->
<!-- android:layout_width="0dp"-->
<!-- android:layout_height="0dp"-->
<!-- android:tint="#00FF00"-->
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
<!-- app:layout_constraintDimensionRatio="W,1:1"-->
<!-- app:layout_constraintEnd_toEndOf="parent"-->
<!-- app:layout_constraintStart_toStartOf="parent"-->
<!-- app:layout_constraintTop_toTopOf="parent"-->
<!-- app:layout_constraintVertical_bias="0.33333"-->
<!-- app:layout_constraintWidth_percent="0.4053398058"-->
<!-- app:srcCompat="@drawable/ic_check_shield" />-->
<TextView
android:id="@+id/text_success"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="12dp"
android:text="Success"
android:textColor="@color/text_light"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/lottie_success" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_success"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="View Wallet"
android:textColor="@color/text_dark"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/text_success"
/>
<androidx.constraintlayout.widget.Group
android:id="@+id/group_success"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:constraint_referenced_ids="lottie_success, text_success, button_success" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_done"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="button_done, text_layout_birthdate" />
<androidx.constraintlayout.widget.Group
android:id="@+id/group_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="text_title, text_subtitle, chips_input, divider_bottom, divider_top" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -156,6 +156,9 @@
app:destination="@id/nav_backup"
app:popUpTo="@id/nav_landing"
app:popUpToInclusive="true"/>
<action
android:id="@+id/action_nav_landing_to_nav_restore"
app:destination="@id/nav_restore" />
</fragment>
<fragment
@ -164,6 +167,17 @@
tools:layout="@layout/fragment_backup" >
</fragment>
<fragment
android:id="@+id/nav_restore"
android:name="cash.z.ecc.android.ui.setup.RestoreFragment"
tools:layout="@layout/fragment_restore" >
<action
android:id="@+id/action_nav_restore_to_nav_home"
app:destination="@id/nav_home"
app:popUpTo="@id/nav_landing"
app:popUpToInclusive="true" />
</fragment>
<!-- -->
<!-- Global actions -->

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -21,6 +21,7 @@ allprojects {
repositories {
google()
jcenter()
maven { url 'https://jitpack.io' }
}
}