Merge pull request #154 from zcash/task/clean-up-dependencies

Task/clean up dependencies
This commit is contained in:
Kevin Gorham 2020-06-10 10:26:14 -04:00 committed by GitHub
commit 41422992ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 479 additions and 300 deletions

View File

@ -5,15 +5,15 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt' apply plugin: 'kotlin-kapt'
apply plugin: 'com.google.gms.google-services' apply plugin: 'com.google.gms.google-services'
apply plugin: 'io.fabric' apply plugin: 'com.google.firebase.crashlytics'
apply plugin: 'com.google.firebase.firebase-perf'
//apply plugin: 'com.github.ben-manes.versions' //apply plugin: 'com.github.ben-manes.versions'
archivesBaseName = 'zcash-android-wallet' archivesBaseName = 'zcash-android-wallet'
group = 'cash.z.ecc.android' group = 'cash.z.ecc.android'
version = '1.0.0-alpha25' version = '1.0.0-alpha28'
android { android {
ndkVersion "21.1.6352462"
compileSdkVersion Deps.compileSdkVersion compileSdkVersion Deps.compileSdkVersion
buildToolsVersion Deps.buildToolsVersion buildToolsVersion Deps.buildToolsVersion
viewBinding.enabled = true viewBinding.enabled = true
@ -27,7 +27,6 @@ android {
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
testInstrumentationRunnerArguments clearPackageData: 'true' testInstrumentationRunnerArguments clearPackageData: 'true'
multiDexEnabled true multiDexEnabled true
// manifestPlaceholders = [rollbarToken: properties["rollbarToken"]]
} }
flavorDimensions 'network' flavorDimensions 'network'
productFlavors { productFlavors {
@ -102,40 +101,46 @@ android {
} }
} }
crashlytics {
enableNdk true
}
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':qrecycler') implementation project(':qrecycler')
implementation project(':feedback') implementation project(':feedback')
implementation project(':mnemonic') implementation project(':mnemonic')
implementation project(':lockbox') implementation project(':lockbox')
implementation project(':sdk')
implementation project(':chipsinputlayout') implementation project(':chipsinputlayout')
// Zcash // Zcash
implementation 'com.github.zcash:zcash-android-wallet-plugins:1.0.0' implementation Deps.Zcash.ANDROID_WALLET_PLUGINS
zcashtestnetImplementation Deps.Zcash.Sdk.TESTNET
zcashmainnetImplementation Deps.Zcash.Sdk.MAINNET
// Kotlin // Kotlin
implementation Deps.Kotlin.STDLIB implementation Deps.Kotlin.STDLIB
// Android // Android
implementation Deps.AndroidX.ANNOTATION
implementation Deps.AndroidX.APPCOMPAT implementation Deps.AndroidX.APPCOMPAT
implementation Deps.AndroidX.CORE_KTX
implementation Deps.AndroidX.CONSTRAINT_LAYOUT implementation Deps.AndroidX.CONSTRAINT_LAYOUT
implementation Deps.AndroidX.Lifecycle.LIFECYCLE_RUNTIME_KTX implementation Deps.AndroidX.CORE_KTX
implementation Deps.AndroidX.FRAGMENT_KTX
implementation Deps.AndroidX.LEGACY
implementation Deps.AndroidX.PAGING
implementation Deps.AndroidX.CameraX.CAMERA2
implementation Deps.AndroidX.CameraX.CORE
implementation Deps.AndroidX.CameraX.LIFECYCLE
implementation Deps.AndroidX.CameraX.View.EXT
implementation Deps.AndroidX.CameraX.View.VIEW
implementation Deps.AndroidX.Lifecycle.LIFECYCLE_EXTENSIONS implementation Deps.AndroidX.Lifecycle.LIFECYCLE_EXTENSIONS
implementation Deps.AndroidX.Lifecycle.LIFECYCLE_RUNTIME_KTX
implementation Deps.AndroidX.Navigation.FRAGMENT_KTX implementation Deps.AndroidX.Navigation.FRAGMENT_KTX
implementation Deps.AndroidX.Navigation.UI_KTX implementation Deps.AndroidX.Navigation.UI_KTX
implementation "androidx.room:room-ktx:2.2.3" implementation Deps.AndroidX.Room.ROOM_KTX
implementation "androidx.paging:paging-runtime-ktx:2.1.1" kapt Deps.AndroidX.Room.ROOM_COMPILER
implementation 'com.google.guava:guava:27.0.1-android'
kapt "androidx.room:room-compiler:2.2.3"
// Google // Google
implementation Deps.Google.GUAVA
implementation Deps.Google.MATERIAL implementation Deps.Google.MATERIAL
implementation Deps.Google.ML_VISION // QR Scanner
// Dagger // Dagger
implementation Deps.Dagger.ANDROID_SUPPORT implementation Deps.Dagger.ANDROID_SUPPORT
@ -143,44 +148,23 @@ dependencies {
kapt Deps.Dagger.COMPILER kapt Deps.Dagger.COMPILER
// grpc-java // grpc-java
implementation "io.grpc:grpc-okhttp:1.25.0" implementation Deps.Grpc.ANDROID
implementation "io.grpc:grpc-android:1.25.0" implementation Deps.Grpc.OKHTTP
implementation "io.grpc:grpc-protobuf-lite:1.25.0" implementation Deps.Grpc.PROTOBUG
implementation "io.grpc:grpc-stub:1.25.0" implementation Deps.Grpc.STUB
implementation 'javax.annotation:javax.annotation-api:1.3.2' implementation Deps.JavaX.JAVA_ANNOTATION
// solves error: Duplicate class com.google.common.util.concurrent.ListenableFuture found in modules jetified-guava-26.0-android.jar (com.google.guava:guava:26.0-android) and listenablefuture-1.0.jar (com.google.guava:listenablefuture:1.0)
// per this recommendation from Chris Povirk, given guava's decision to split ListenableFuture away from Guava: https://groups.google.com/d/msg/guava-discuss/GghaKwusjcY/bCIAKfzOEwAJ
implementation 'com.google.guava:guava:27.0.1-android'
// Analytics // Analytics (for dogfooding/crash-reporting/feedback only on internal team builds)
implementation 'com.mixpanel.android:mixpanel-android:5.6.3' implementation Deps.Analytics.CRASHLYTICS
implementation 'com.google.firebase:firebase-analytics:17.2.2' implementation Deps.Analytics.MIXPANEL
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
implementation 'com.crashlytics.sdk.android:crashlytics-ndk:2.1.1'
implementation 'com.google.firebase:firebase-perf:19.0.5'
// QR Scanning
implementation 'com.google.firebase:firebase-ml-vision:24.0.1'
implementation 'androidx.camera:camera-core:1.0.0-alpha10'
implementation 'androidx.camera:camera-camera2:1.0.0-alpha10'
implementation "androidx.camera:camera-view:1.0.0-alpha07"
implementation "androidx.camera:camera-extensions:1.0.0-alpha07"
implementation "androidx.camera:camera-lifecycle:1.0.0-alpha10"
// Misc. // Misc.
implementation 'com.airbnb.android:lottie:3.1.0' implementation Deps.Misc.LOTTIE
implementation 'com.facebook.stetho:stetho:1.5.1'
// check for build errors at https://jitpack.io/com/github/gmale/chips-input-layout/<version>/build.log
// implementation 'com.github.gmale:chips-input-layout:4750760a7222bc04ca48266b387456d2b03541a7'
implementation 'androidx.annotation:annotation:1.1.0'
// Tests // Tests
testImplementation Deps.Test.COROUTINES_TEST
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation Deps.Test.JUNIT testImplementation Deps.Test.JUNIT
testImplementation "com.nhaarman.mockitokotlin2:mockito-kotlin:2.2.0" testImplementation Deps.Test.MOKITO
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.3'
androidTestImplementation Deps.Test.Android.JUNIT androidTestImplementation Deps.Test.Android.JUNIT
androidTestImplementation Deps.Test.Android.ESPRESSO androidTestImplementation Deps.Test.Android.ESPRESSO
} }

View File

@ -5,7 +5,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry import androidx.test.platform.app.InstrumentationRegistry
import cash.z.ecc.android.lockbox.LockBox import cash.z.ecc.android.lockbox.LockBox
import cash.z.ecc.kotlin.mnemonic.Mnemonics import cash.z.ecc.kotlin.mnemonic.Mnemonics
import cash.z.wallet.sdk.Initializer import cash.z.ecc.android.sdk.Initializer
import okio.Buffer import okio.Buffer
import okio.GzipSink import okio.GzipSink
import okio.Okio import okio.Okio

View File

@ -33,6 +33,8 @@
<!-- Firebase options --> <!-- Firebase options -->
<meta-data android:name="com.google.firebase.ml.vision.DEPENDENCIES" android:value="barcode" /> <meta-data android:name="com.google.firebase.ml.vision.DEPENDENCIES" android:value="barcode" />
<!-- All reporting is opt-in, only -->
<meta-data android:name="firebase_crashlytics_collection_enabled" android:value="false" />
<!-- Mixpanel options --> <!-- Mixpanel options -->
<meta-data android:name="com.mixpanel.android.MPConfig.AutoShowMixpanelUpdates" android:value="false" /> <meta-data android:name="com.mixpanel.android.MPConfig.AutoShowMixpanelUpdates" android:value="false" />

View File

@ -8,10 +8,10 @@ import androidx.camera.core.CameraXConfig
import cash.z.ecc.android.di.component.AppComponent import cash.z.ecc.android.di.component.AppComponent
import cash.z.ecc.android.di.component.DaggerAppComponent import cash.z.ecc.android.di.component.DaggerAppComponent
import cash.z.ecc.android.feedback.FeedbackCoordinator import cash.z.ecc.android.feedback.FeedbackCoordinator
import cash.z.wallet.sdk.ext.SilentTwig import cash.z.ecc.android.sdk.ext.SilentTwig
import cash.z.wallet.sdk.ext.TroubleshootingTwig import cash.z.ecc.android.sdk.ext.TroubleshootingTwig
import cash.z.wallet.sdk.ext.Twig import cash.z.ecc.android.sdk.ext.Twig
import cash.z.wallet.sdk.ext.twig import cash.z.ecc.android.sdk.ext.twig
import kotlinx.coroutines.* import kotlinx.coroutines.*
import javax.inject.Inject import javax.inject.Inject
@ -37,7 +37,6 @@ class ZcashWalletApp : Application(), CameraXConfig.Provider {
override fun onCreate() { override fun onCreate() {
Thread.setDefaultUncaughtExceptionHandler(ExceptionReporter(Thread.getDefaultUncaughtExceptionHandler())) Thread.setDefaultUncaughtExceptionHandler(ExceptionReporter(Thread.getDefaultUncaughtExceptionHandler()))
Twig.plant(TroubleshootingTwig())
creationTime = System.currentTimeMillis() creationTime = System.currentTimeMillis()
instance = this instance = this
// Setup handler for uncaught exceptions. // Setup handler for uncaught exceptions.

View File

@ -4,7 +4,7 @@ import cash.z.ecc.android.ZcashWalletApp
import cash.z.ecc.android.di.annotation.ActivityScope import cash.z.ecc.android.di.annotation.ActivityScope
import cash.z.ecc.android.di.annotation.SynchronizerScope import cash.z.ecc.android.di.annotation.SynchronizerScope
import cash.z.ecc.android.di.module.InitializerModule import cash.z.ecc.android.di.module.InitializerModule
import cash.z.wallet.sdk.Initializer import cash.z.ecc.android.sdk.Initializer
import dagger.BindsInstance import dagger.BindsInstance
import dagger.Subcomponent import dagger.Subcomponent

View File

@ -3,8 +3,8 @@ package cash.z.ecc.android.di.component
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import cash.z.ecc.android.di.annotation.SynchronizerScope import cash.z.ecc.android.di.annotation.SynchronizerScope
import cash.z.ecc.android.di.module.SynchronizerModule import cash.z.ecc.android.di.module.SynchronizerModule
import cash.z.wallet.sdk.Initializer import cash.z.ecc.android.sdk.Initializer
import cash.z.wallet.sdk.Synchronizer import cash.z.ecc.android.sdk.Synchronizer
import dagger.BindsInstance import dagger.BindsInstance
import dagger.Subcomponent import dagger.Subcomponent
import javax.inject.Named import javax.inject.Named

View File

@ -2,9 +2,14 @@ package cash.z.ecc.android.di.module
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.SharedPreferences
import cash.z.ecc.android.ZcashWalletApp import cash.z.ecc.android.ZcashWalletApp
import cash.z.ecc.android.di.component.MainActivitySubcomponent import cash.z.ecc.android.di.component.MainActivitySubcomponent
import cash.z.ecc.android.feedback.* import cash.z.ecc.android.feedback.*
import cash.z.ecc.android.sdk.ext.SilentTwig
import cash.z.ecc.android.sdk.ext.TroubleshootingTwig
import cash.z.ecc.android.sdk.ext.Twig
import com.google.firebase.crashlytics.FirebaseCrashlytics
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.multibindings.IntoSet import dagger.multibindings.IntoSet
@ -27,6 +32,11 @@ class AppModule {
// Feedback // Feedback
// //
@Provides
@Singleton
fun providePreferences(context: Context): SharedPreferences
= context.getSharedPreferences("Application", Context.MODE_PRIVATE)
@Provides @Provides
@Singleton @Singleton
fun provideFeedback(): Feedback = Feedback() fun provideFeedback(): Feedback = Feedback()
@ -35,8 +45,16 @@ class AppModule {
@Singleton @Singleton
fun provideFeedbackCoordinator( fun provideFeedbackCoordinator(
feedback: Feedback, feedback: Feedback,
preferences: SharedPreferences,
defaultObservers: Set<@JvmSuppressWildcards FeedbackCoordinator.FeedbackObserver> defaultObservers: Set<@JvmSuppressWildcards FeedbackCoordinator.FeedbackObserver>
): FeedbackCoordinator = FeedbackCoordinator(feedback, defaultObservers) ): FeedbackCoordinator {
return preferences.getBoolean(FeedbackCoordinator.ENABLED, false).let { isEnabled ->
// observe nothing unless feedback is enabled
FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(isEnabled)
Twig.plant(if (isEnabled) TroubleshootingTwig() else SilentTwig())
FeedbackCoordinator(feedback, if (isEnabled) defaultObservers else setOf())
}
}
// //

View File

@ -1,7 +1,7 @@
package cash.z.ecc.android.di.module package cash.z.ecc.android.di.module
import android.content.Context import android.content.Context
import cash.z.wallet.sdk.Initializer import cash.z.ecc.android.sdk.Initializer
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.Reusable import dagger.Reusable

View File

@ -2,8 +2,8 @@ package cash.z.ecc.android.di.module
import android.content.Context import android.content.Context
import cash.z.ecc.android.di.annotation.SynchronizerScope import cash.z.ecc.android.di.annotation.SynchronizerScope
import cash.z.wallet.sdk.Initializer import cash.z.ecc.android.sdk.Initializer
import cash.z.wallet.sdk.Synchronizer import cash.z.ecc.android.sdk.Synchronizer
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides

View File

@ -0,0 +1,97 @@
package cash.z.ecc.android.ext
import android.app.ActivityManager
import android.app.Dialog
import android.content.Context
import android.content.Intent
import android.provider.Settings
import androidx.core.content.getSystemService
import com.google.android.material.dialog.MaterialAlertDialogBuilder
fun Context.showClearDataConfirmation(onDismiss: () -> Unit = {}, onCancel: () -> Unit = {}): Dialog {
return MaterialAlertDialogBuilder(this)
.setTitle("Nuke Wallet?")
.setMessage("WARNING: Potential Loss of Funds\n\nClearing all wallet data and can result in a loss of funds, if you cannot locate your correct seed phrase.\n\nPlease confirm that you have your 24-word seed phrase available before proceeding.")
.setCancelable(false)
.setPositiveButton("Cancel") { dialog, _ ->
dialog.dismiss()
onDismiss()
onCancel()
}
.setNegativeButton("Erase Wallet") { dialog, _ ->
dialog.dismiss()
onDismiss()
getSystemService<ActivityManager>()?.clearApplicationUserData()
}
.show()
}
fun Context.showUninitializedError(error: Throwable? = null, onDismiss: () -> Unit = {}): Dialog {
return MaterialAlertDialogBuilder(this)
.setTitle("Wallet Improperly Initialized")
.setMessage("This wallet has not been initialized correctly! Perhaps an error occurred during install.\n\nThis can be fixed with a reset. First, locate your backup seed phrase, then CLEAR DATA and reimport it.")
.setCancelable(false)
.setPositiveButton("Exit") { dialog, _ ->
dialog.dismiss()
onDismiss()
if (error != null) throw error
}
.setNegativeButton("Clear Data") { dialog, _ ->
showClearDataConfirmation(onDismiss, onCancel = {
// do not let the user back into the app because we cannot recover from this case
showUninitializedError(error, onDismiss)
})
}
.show()
}
fun Context.showInvalidSeedPhraseError(error: Throwable? = null, onDismiss: () -> Unit = {}): Dialog {
return MaterialAlertDialogBuilder(this)
.setTitle("Oops! Invalid Seed Phrase")
.setMessage("That seed phrase appears to be invalid! Please double-check it and try again.\n\n${error?.message ?: ""}")
.setCancelable(false)
.setPositiveButton("Retry") { dialog, _ ->
dialog.dismiss()
onDismiss()
}
.show()
}
fun Context.showScanFailure(error: Throwable?, onCancel: () -> Unit = {}, onDismiss: () -> Unit = {}): Dialog {
val message = if (error == null) {
"Unknown error"
} else {
"${error.message}${if (error.cause != null) "\n\nCaused by: ${error.cause}" else ""}"
}
return MaterialAlertDialogBuilder(this)
.setTitle("Scan Failure")
.setMessage(message)
.setCancelable(true)
.setPositiveButton("Retry") { d, _ ->
d.dismiss()
onDismiss()
}
.setNegativeButton("Ignore") { d, _ ->
d.dismiss()
onCancel()
onDismiss()
}
.show()
}
fun Context.showCriticalProcessorError(error: Throwable?, onRetry: () -> Unit = {}): Dialog {
return MaterialAlertDialogBuilder(this)
.setTitle("Processor Error")
.setMessage(error?.message ?: "Critical error while processing blocks!")
.setCancelable(false)
.setPositiveButton("Retry") { d, _ ->
d.dismiss()
onRetry()
}
.setNegativeButton("Exit") { dialog, _ ->
dialog.dismiss()
throw error ?: RuntimeException("Critical error while processing blocks and the user chose to exit.")
}
.show()
}

View File

@ -3,9 +3,9 @@ package cash.z.ecc.android.ext
import android.view.inputmethod.EditorInfo.IME_ACTION_DONE import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
import android.widget.EditText import android.widget.EditText
import android.widget.TextView import android.widget.TextView
import cash.z.wallet.sdk.ext.convertZecToZatoshi import cash.z.ecc.android.sdk.ext.convertZecToZatoshi
import cash.z.wallet.sdk.ext.safelyConvertToBigDecimal import cash.z.ecc.android.sdk.ext.safelyConvertToBigDecimal
import cash.z.wallet.sdk.ext.twig import cash.z.ecc.android.sdk.ext.twig
fun EditText.onEditorActionDone(block: (EditText) -> Unit) { fun EditText.onEditorActionDone(block: (EditText) -> Unit) {
this.setOnEditorActionListener { _, actionId, _ -> this.setOnEditorActionListener { _, actionId, _ ->

View File

@ -1,6 +1,6 @@
package cash.z.ecc.android.feedback package cash.z.ecc.android.feedback
import com.crashlytics.android.Crashlytics import com.google.firebase.crashlytics.FirebaseCrashlytics
class FeedbackCrashlytics : FeedbackCoordinator.FeedbackObserver { class FeedbackCrashlytics : FeedbackCoordinator.FeedbackObserver {
/** /**
@ -18,7 +18,7 @@ class FeedbackCrashlytics : FeedbackCoordinator.FeedbackObserver {
) )
else -> null else -> null
} }
exception?.let { Crashlytics.logException(it) } exception?.let { FirebaseCrashlytics.getInstance().recordException(it) }
} }
private class ReorgException(errorHeight: Int, rewindHeight: Int, reorgMesssage: String) : private class ReorgException(errorHeight: Int, rewindHeight: Int, reorgMesssage: String) :

View File

@ -63,6 +63,7 @@ object Report {
val errorHeight: Int by propertyMap val errorHeight: Int by propertyMap
val rewindHeight: Int by propertyMap val rewindHeight: Int by propertyMap
} }
class TxUpdateFailed(t: Throwable) : Feedback.AppError("txupdate", t, false)
} }
} }

View File

@ -38,10 +38,10 @@ import cash.z.ecc.android.feedback.Report.Error.NonFatal.Reorg
import cash.z.ecc.android.feedback.Report.NonUserAction.FEEDBACK_STOPPED import cash.z.ecc.android.feedback.Report.NonUserAction.FEEDBACK_STOPPED
import cash.z.ecc.android.feedback.Report.NonUserAction.SYNC_START import cash.z.ecc.android.feedback.Report.NonUserAction.SYNC_START
import cash.z.ecc.android.feedback.Report.Tap.COPY_ADDRESS import cash.z.ecc.android.feedback.Report.Tap.COPY_ADDRESS
import cash.z.wallet.sdk.Initializer import cash.z.ecc.android.sdk.Initializer
import cash.z.wallet.sdk.exception.CompactBlockProcessorException import cash.z.ecc.android.sdk.exception.CompactBlockProcessorException
import cash.z.wallet.sdk.ext.ZcashSdk import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.wallet.sdk.ext.twig import cash.z.ecc.android.sdk.ext.twig
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -224,6 +224,13 @@ class MainActivity : AppCompatActivity() {
} }
} }
suspend fun isValidAddress(address: String): Boolean {
try {
return !synchronizerComponent.synchronizer().validateAddress(address).isNotValid
} catch (t: Throwable) { }
return false
}
fun copyText(textToCopy: String, label: String = "zECC Wallet Text") { fun copyText(textToCopy: String, label: String = "zECC Wallet Text") {
clipboard.setPrimaryClip( clipboard.setPrimaryClip(
ClipData.newPlainText(label, textToCopy) ClipData.newPlainText(label, textToCopy)

View File

@ -5,7 +5,7 @@ import android.view.ViewGroup
import androidx.paging.PagedListAdapter import androidx.paging.PagedListAdapter
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import cash.z.ecc.android.R import cash.z.ecc.android.R
import cash.z.wallet.sdk.entity.ConfirmedTransaction import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
class TransactionAdapter<T : ConfirmedTransaction> : class TransactionAdapter<T : ConfirmedTransaction> :
PagedListAdapter<T, TransactionViewHolder<T>>( PagedListAdapter<T, TransactionViewHolder<T>>(

View File

@ -3,6 +3,7 @@ package cash.z.ecc.android.ui.detail
import android.view.View import android.view.View
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import cash.z.ecc.android.R import cash.z.ecc.android.R
import cash.z.ecc.android.ext.goneIf import cash.z.ecc.android.ext.goneIf
@ -11,9 +12,10 @@ import cash.z.ecc.android.ui.MainActivity
import cash.z.ecc.android.ui.send.SendViewModel import cash.z.ecc.android.ui.send.SendViewModel
import cash.z.ecc.android.ui.util.INCLUDE_MEMO_PREFIX import cash.z.ecc.android.ui.util.INCLUDE_MEMO_PREFIX
import cash.z.ecc.android.ui.util.toUtf8Memo import cash.z.ecc.android.ui.util.toUtf8Memo
import cash.z.wallet.sdk.entity.ConfirmedTransaction import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
import cash.z.wallet.sdk.ext.* import cash.z.ecc.android.sdk.ext.*
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.launch
import java.nio.charset.Charset import java.nio.charset.Charset
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
@ -28,88 +30,89 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
private val addressRegex = """zs\d\w{65,}""".toRegex() private val addressRegex = """zs\d\w{65,}""".toRegex()
fun bindTo(transaction: T?) { fun bindTo(transaction: T?) {
(itemView.context as MainActivity).lifecycleScope.launch {
// update view
var lineOne: String = ""
var lineTwo: String = ""
var amountZec: String = ""
var amountDisplay: String = ""
var amountColor: Int = R.color.text_light_dimmed
var lineOneColor: Int = R.color.text_light
var lineTwoColor: Int = R.color.text_light_dimmed
var indicatorBackground: Int = R.drawable.background_indicator_unknown
// update view transaction?.apply {
var lineOne: String = "" itemView.setOnClickListener {
var lineTwo: String = "" onTransactionClicked(this)
var amountZec: String = "" }
var amountDisplay: String = "" itemView.setOnLongClickListener {
var amountColor: Int = R.color.text_light_dimmed onTransactionLongPressed(this)
var lineOneColor: Int = R.color.text_light true
var lineTwoColor: Int = R.color.text_light_dimmed }
var indicatorBackground: Int = R.drawable.background_indicator_unknown amountZec = value.convertZatoshiToZecString()
// TODO: these might be good extension functions
transaction?.apply { val timestamp = formatter.format(blockTimeInSeconds * 1000L)
itemView.setOnClickListener { val isMined = blockTimeInSeconds != 0L
onTransactionClicked(this) when {
} !toAddress.isNullOrEmpty() -> {
itemView.setOnLongClickListener { lineOne = "You paid ${toAddress?.toAbbreviatedAddress()}"
onTransactionLongPressed(this) lineTwo = if (isMined) "Sent $timestamp" else "Pending confirmation"
true amountDisplay = "- $amountZec"
} if (isMined) {
amountZec = value.convertZatoshiToZecString() amountColor = R.color.zcashRed
// TODO: these might be good extension functions indicatorBackground = R.drawable.background_indicator_outbound
val timestamp = formatter.format(blockTimeInSeconds * 1000L) } else {
val isMined = blockTimeInSeconds != 0L lineOneColor = R.color.text_light_dimmed
when { lineTwoColor = R.color.text_light
!toAddress.isNullOrEmpty() -> { }
lineOne = "You paid ${toAddress?.toAbbreviatedAddress()}" }
lineTwo = if (isMined) "Sent $timestamp" else "Pending confirmation" toAddress.isNullOrEmpty() && value > 0L && minedHeight > 0 -> {
amountDisplay = "- $amountZec" lineOne = getSender(transaction)
if (isMined) { lineTwo = "Received $timestamp"
amountColor = R.color.zcashRed amountDisplay = "+ $amountZec"
indicatorBackground = R.drawable.background_indicator_outbound amountColor = R.color.zcashGreen
} else { indicatorBackground = R.drawable.background_indicator_inbound
lineOneColor = R.color.text_light_dimmed }
lineTwoColor = R.color.text_light else -> {
lineOne = "Unknown"
lineTwo = "Unknown"
amountDisplay = "$amountZec"
amountColor = R.color.text_light
} }
} }
toAddress.isNullOrEmpty() && value > 0L && minedHeight > 0 -> { // sanitize amount
lineOne = getSender(transaction) if (value < ZcashSdk.MINERS_FEE_ZATOSHI) amountDisplay = "< 0.001"
lineTwo = "Received $timestamp" else if (amountZec.length > 10) { // 10 allows 3 digits to the left and 6 to the right of the decimal
amountDisplay = "+ $amountZec" amountDisplay = "tap to view"
amountColor = R.color.zcashGreen
indicatorBackground = R.drawable.background_indicator_inbound
}
else -> {
lineOne = "Unknown"
lineTwo = "Unknown"
amountDisplay = "$amountZec"
amountColor = R.color.text_light
} }
} }
// sanitize amount
if (value < ZcashSdk.MINERS_FEE_ZATOSHI) amountDisplay = "< 0.001"
else if (amountZec.length > 10) { // 10 allows 3 digits to the left and 6 to the right of the decimal topText.text = lineOne
amountDisplay = "tap to view" bottomText.text = lineTwo
} amountText.text = amountDisplay
amountText.setTextColor(amountColor.toAppColor())
topText.setTextColor(lineOneColor.toAppColor())
bottomText.setTextColor(lineTwoColor.toAppColor())
val context = itemView.context
indicator.background = context.resources.getDrawable(indicatorBackground)
shieldIcon.goneIf((transaction?.raw != null || transaction?.expiryHeight != null) && !transaction?.toAddress.isShielded())
} }
topText.text = lineOne
bottomText.text = lineTwo
amountText.text = amountDisplay
amountText.setTextColor(amountColor.toAppColor())
topText.setTextColor(lineOneColor.toAppColor())
bottomText.setTextColor(lineTwoColor.toAppColor())
val context = itemView.context
indicator.background = context.resources.getDrawable(indicatorBackground)
shieldIcon.goneIf((transaction?.raw != null || transaction?.expiryHeight != null) && !transaction?.toAddress.isShielded())
} }
private fun getSender(transaction: ConfirmedTransaction): String { private suspend fun getSender(transaction: ConfirmedTransaction): String {
val memo = transaction.memo.toUtf8Memo() val memo = transaction.memo.toUtf8Memo()
return when { return when {
memo.contains(INCLUDE_MEMO_PREFIX) -> { memo.contains(INCLUDE_MEMO_PREFIX) -> {
val address = memo.split(INCLUDE_MEMO_PREFIX)[1].trim() val address = memo.split(INCLUDE_MEMO_PREFIX)[1].trim().validateAddress() ?: "Unknown"
"${address.toAbbreviatedAddress()} paid you" "${address.toAbbreviatedAddress()} paid you"
} }
memo.contains("eply to:") -> { memo.contains("eply to:") -> {
val address = memo.split("eply to:")[1].trim() val address = memo.split("eply to:")[1].trim().validateAddress() ?: "Unknown"
"${address.toAbbreviatedAddress()} paid you" "${address.toAbbreviatedAddress()} paid you"
} }
memo.contains("zs") -> { memo.contains("zs") -> {
val who = extractAddress(memo)?.toAbbreviatedAddress() ?: "Unknown" val who = extractAddress(memo).validateAddress()?.toAbbreviatedAddress() ?: "Unknown"
"$who paid you" "$who paid you"
} }
else -> "Unknown paid you" else -> "Unknown paid you"
@ -145,6 +148,11 @@ class TransactionViewHolder<T : ConfirmedTransaction>(itemView: View) : Recycler
(itemView.context as MainActivity).copyText(it, "Transaction Address") (itemView.context as MainActivity).copyText(it, "Transaction Address")
} }
} }
private suspend fun String?.validateAddress(): String? {
if (this == null) return null
return if ((itemView.context as MainActivity).isValidAddress(this)) this else null
}
} }
private fun ByteArray.toTxId(): String { private fun ByteArray.toTxId(): String {

View File

@ -15,12 +15,12 @@ import cash.z.ecc.android.ext.toColoredSpan
import cash.z.ecc.android.feedback.Report import cash.z.ecc.android.feedback.Report
import cash.z.ecc.android.feedback.Report.Tap.DETAIL_BACK import cash.z.ecc.android.feedback.Report.Tap.DETAIL_BACK
import cash.z.ecc.android.ui.base.BaseFragment import cash.z.ecc.android.ui.base.BaseFragment
import cash.z.wallet.sdk.block.CompactBlockProcessor.WalletBalance import cash.z.ecc.android.sdk.block.CompactBlockProcessor.WalletBalance
import cash.z.wallet.sdk.entity.ConfirmedTransaction import cash.z.ecc.android.sdk.db.entity.ConfirmedTransaction
import cash.z.wallet.sdk.ext.collectWith import cash.z.ecc.android.sdk.ext.collectWith
import cash.z.wallet.sdk.ext.convertZatoshiToZecString import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString
import cash.z.wallet.sdk.ext.toAbbreviatedAddress import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress
import cash.z.wallet.sdk.ext.twig import cash.z.ecc.android.sdk.ext.twig
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

View File

@ -1,8 +1,8 @@
package cash.z.ecc.android.ui.detail package cash.z.ecc.android.ui.detail
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import cash.z.wallet.sdk.Synchronizer import cash.z.ecc.android.sdk.Synchronizer
import cash.z.wallet.sdk.ext.twig import cash.z.ecc.android.sdk.ext.twig
import javax.inject.Inject import javax.inject.Inject
class WalletDetailViewModel @Inject constructor() : ViewModel() { class WalletDetailViewModel @Inject constructor() : ViewModel() {

View File

@ -18,13 +18,13 @@ import cash.z.ecc.android.ui.home.HomeFragment.BannerAction.*
import cash.z.ecc.android.ui.send.SendViewModel import cash.z.ecc.android.ui.send.SendViewModel
import cash.z.ecc.android.ui.setup.WalletSetupViewModel import cash.z.ecc.android.ui.setup.WalletSetupViewModel
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.NO_SEED import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.NO_SEED
import cash.z.wallet.sdk.Synchronizer import cash.z.ecc.android.sdk.Synchronizer
import cash.z.wallet.sdk.Synchronizer.Status.* import cash.z.ecc.android.sdk.Synchronizer.Status.*
import cash.z.wallet.sdk.block.CompactBlockProcessor import cash.z.ecc.android.sdk.block.CompactBlockProcessor
import cash.z.wallet.sdk.ext.convertZatoshiToZecString import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString
import cash.z.wallet.sdk.ext.convertZecToZatoshi import cash.z.ecc.android.sdk.ext.convertZecToZatoshi
import cash.z.wallet.sdk.ext.safelyConvertToBigDecimal import cash.z.ecc.android.sdk.ext.safelyConvertToBigDecimal
import cash.z.wallet.sdk.ext.twig import cash.z.ecc.android.sdk.ext.twig
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*

View File

@ -1,14 +1,14 @@
package cash.z.ecc.android.ui.home package cash.z.ecc.android.ui.home
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import cash.z.wallet.sdk.SdkSynchronizer import cash.z.ecc.android.sdk.SdkSynchronizer
import cash.z.wallet.sdk.Synchronizer import cash.z.ecc.android.sdk.Synchronizer
import cash.z.wallet.sdk.Synchronizer.Status.* import cash.z.ecc.android.sdk.Synchronizer.Status.*
import cash.z.wallet.sdk.block.CompactBlockProcessor import cash.z.ecc.android.sdk.block.CompactBlockProcessor
import cash.z.wallet.sdk.exception.RustLayerException import cash.z.ecc.android.sdk.exception.RustLayerException
import cash.z.wallet.sdk.ext.ZcashSdk.MINERS_FEE_ZATOSHI import cash.z.ecc.android.sdk.ext.ZcashSdk.MINERS_FEE_ZATOSHI
import cash.z.wallet.sdk.ext.ZcashSdk.ZATOSHI_PER_ZEC import cash.z.ecc.android.sdk.ext.ZcashSdk.ZATOSHI_PER_ZEC
import cash.z.wallet.sdk.ext.twig import cash.z.ecc.android.sdk.ext.twig
import kotlinx.coroutines.channels.ConflatedBroadcastChannel import kotlinx.coroutines.channels.ConflatedBroadcastChannel
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import javax.inject.Inject import javax.inject.Inject

View File

@ -1,7 +1,7 @@
package cash.z.ecc.android.ui.home package cash.z.ecc.android.ui.home
import android.animation.ValueAnimator import android.animation.ValueAnimator
import cash.z.wallet.sdk.ext.twig import cash.z.ecc.android.sdk.ext.twig
import com.airbnb.lottie.LottieAnimationView import com.airbnb.lottie.LottieAnimationView
class MagicSnakeLoader( class MagicSnakeLoader(

View File

@ -19,8 +19,8 @@ import cash.z.ecc.android.feedback.Report
import cash.z.ecc.android.feedback.Report.Funnel.UserFeedback import cash.z.ecc.android.feedback.Report.Funnel.UserFeedback
import cash.z.ecc.android.feedback.Report.Tap.* import cash.z.ecc.android.feedback.Report.Tap.*
import cash.z.ecc.android.ui.base.BaseFragment import cash.z.ecc.android.ui.base.BaseFragment
import cash.z.wallet.sdk.ext.toAbbreviatedAddress import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress
import cash.z.wallet.sdk.ext.twig import cash.z.ecc.android.sdk.ext.twig
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import okio.Okio import okio.Okio
import java.io.File import java.io.File

View File

@ -1,8 +1,8 @@
package cash.z.ecc.android.ui.profile package cash.z.ecc.android.ui.profile
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import cash.z.wallet.sdk.Synchronizer import cash.z.ecc.android.sdk.Synchronizer
import cash.z.wallet.sdk.ext.twig import cash.z.ecc.android.sdk.ext.twig
import javax.inject.Inject import javax.inject.Inject
class ProfileViewModel @Inject constructor() : ViewModel() { class ProfileViewModel @Inject constructor() : ViewModel() {

View File

@ -13,8 +13,8 @@ import cash.z.ecc.android.ext.onClickNavTo
import cash.z.ecc.android.feedback.Report import cash.z.ecc.android.feedback.Report
import cash.z.ecc.android.feedback.Report.Tap.* import cash.z.ecc.android.feedback.Report.Tap.*
import cash.z.ecc.android.ui.base.BaseFragment import cash.z.ecc.android.ui.base.BaseFragment
import cash.z.wallet.sdk.ext.toAbbreviatedAddress import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress
import cash.z.wallet.sdk.ext.twig import cash.z.ecc.android.sdk.ext.twig
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.math.roundToInt import kotlin.math.roundToInt

View File

@ -1,8 +1,8 @@
package cash.z.ecc.android.ui.receive package cash.z.ecc.android.ui.receive
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import cash.z.wallet.sdk.Synchronizer import cash.z.ecc.android.sdk.Synchronizer
import cash.z.wallet.sdk.ext.twig import cash.z.ecc.android.sdk.ext.twig
import javax.inject.Inject import javax.inject.Inject
class ReceiveViewModel @Inject constructor() : ViewModel() { class ReceiveViewModel @Inject constructor() : ViewModel() {

View File

@ -2,9 +2,9 @@ package cash.z.ecc.android.ui.scan
import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy import androidx.camera.core.ImageProxy
import cash.z.wallet.sdk.ext.retrySimple import cash.z.ecc.android.sdk.ext.retrySimple
import cash.z.wallet.sdk.ext.retryUpTo import cash.z.ecc.android.sdk.ext.retryUpTo
import cash.z.wallet.sdk.ext.twig import cash.z.ecc.android.sdk.ext.twig
import com.google.android.gms.tasks.Task import com.google.android.gms.tasks.Task
import com.google.firebase.ml.vision.FirebaseVision import com.google.firebase.ml.vision.FirebaseVision
import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcode import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcode

View File

@ -3,12 +3,10 @@ package cash.z.ecc.android.ui.scan
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import androidx.camera.core.CameraSelector import androidx.camera.core.*
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.ImageProxy
import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import cash.z.ecc.android.R import cash.z.ecc.android.R
@ -18,11 +16,15 @@ import cash.z.ecc.android.di.viewmodel.viewModel
import cash.z.ecc.android.ext.onClickNavBack import cash.z.ecc.android.ext.onClickNavBack
import cash.z.ecc.android.ext.onClickNavTo import cash.z.ecc.android.ext.onClickNavTo
import cash.z.ecc.android.feedback.Report import cash.z.ecc.android.feedback.Report
import cash.z.ecc.android.feedback.Report.Tap.* import cash.z.ecc.android.feedback.Report.Tap.SCAN_BACK
import cash.z.ecc.android.feedback.Report.Tap.SCAN_RECEIVE
import cash.z.ecc.android.sdk.ext.twig
import cash.z.ecc.android.ui.base.BaseFragment import cash.z.ecc.android.ui.base.BaseFragment
import cash.z.ecc.android.ui.send.SendViewModel import cash.z.ecc.android.ui.send.SendViewModel
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import com.google.firebase.crashlytics.FirebaseCrashlytics
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors import java.util.concurrent.Executors
class ScanFragment : BaseFragment<FragmentScanBinding>() { class ScanFragment : BaseFragment<FragmentScanBinding>() {
@ -33,11 +35,15 @@ class ScanFragment : BaseFragment<FragmentScanBinding>() {
private lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider> private lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
private var cameraExecutor: ExecutorService? = null
override fun inflate(inflater: LayoutInflater): FragmentScanBinding = override fun inflate(inflater: LayoutInflater): FragmentScanBinding =
FragmentScanBinding.inflate(inflater) FragmentScanBinding.inflate(inflater)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
if (cameraExecutor != null) cameraExecutor?.shutdown()
cameraExecutor = Executors.newSingleThreadExecutor()
binding.buttonReceive.onClickNavTo(R.id.action_nav_scan_to_nav_receive) { tapped(SCAN_RECEIVE) } binding.buttonReceive.onClickNavTo(R.id.action_nav_scan_to_nav_receive) { tapped(SCAN_RECEIVE) }
binding.backButtonHitArea.onClickNavBack() { tapped(SCAN_BACK) } binding.backButtonHitArea.onClickNavBack() { tapped(SCAN_BACK) }
@ -56,26 +62,67 @@ class ScanFragment : BaseFragment<FragmentScanBinding>() {
}, ContextCompat.getMainExecutor(context)) }, ContextCompat.getMainExecutor(context))
} }
override fun onDestroyView() {
super.onDestroyView()
cameraExecutor?.shutdown()
cameraExecutor = null
}
private fun bindPreview(cameraProvider: ProcessCameraProvider) { private fun bindPreview(cameraProvider: ProcessCameraProvider) {
Preview.Builder().setTargetName("Preview").build().let { preview -> // Most of the code here is adapted from: https://github.com/android/camera-samples/blob/master/CameraXBasic/app/src/main/java/com/android/example/cameraxbasic/fragments/CameraFragment.kt
preview.setSurfaceProvider(binding.preview.previewSurfaceProvider) // it's worth keeping tabs on that implementation because they keep making breaking changes to these APIs!
val cameraSelector = CameraSelector.Builder() // Get screen metrics used to setup camera for full screen resolution
.requireLensFacing(CameraSelector.LENS_FACING_BACK) val metrics = DisplayMetrics().also { binding.preview.display.getRealMetrics(it) }
.build() val screenAspectRatio = aspectRatio(metrics.widthPixels, metrics.heightPixels)
val rotation = binding.preview.display.rotation
val imageAnalysis = ImageAnalysis.Builder() val preview =
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) Preview.Builder().setTargetName("Preview").setTargetAspectRatio(screenAspectRatio)
.build() .setTargetRotation(rotation).build()
imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor(), QrAnalyzer { q, i -> val cameraSelector = CameraSelector.Builder()
onQrScanned(q, i) .requireLensFacing(CameraSelector.LENS_FACING_BACK)
}) .build()
val imageAnalysis = ImageAnalysis.Builder().setTargetAspectRatio(screenAspectRatio)
.setTargetRotation(rotation)
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
imageAnalysis.setAnalyzer(cameraExecutor!!, QrAnalyzer { q, i ->
onQrScanned(q, i)
})
// Must unbind the use-cases before rebinding them
cameraProvider.unbindAll()
try {
cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalysis) cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalysis)
preview.setSurfaceProvider(binding.preview.createSurfaceProvider())
} catch (t: Throwable) {
// TODO: consider bubbling this up to the user
mainActivity?.feedback?.report(t)
twig("Error while opening the camera: $t")
} }
} }
/**
* Adapted from: https://github.com/android/camera-samples/blob/master/CameraXBasic/app/src/main/java/com/android/example/cameraxbasic/fragments/CameraFragment.kt#L350
*/
private fun aspectRatio(width: Int, height: Int): Int {
val previewRatio = kotlin.math.max(width, height).toDouble() / kotlin.math.min(
width,
height
)
if (kotlin.math.abs(previewRatio - (4.0 / 3.0))
<= kotlin.math.abs(previewRatio - (16.0 / 9.0))) {
return AspectRatio.RATIO_4_3
}
return AspectRatio.RATIO_16_9
}
private fun onQrScanned(qrContent: String, image: ImageProxy) { private fun onQrScanned(qrContent: String, image: ImageProxy) {
resumedScope.launch { resumedScope.launch {
if (viewModel.isNotValid(qrContent)) image.close() // continue scanning if (viewModel.isNotValid(qrContent)) image.close() // continue scanning

View File

@ -1,8 +1,8 @@
package cash.z.ecc.android.ui.scan package cash.z.ecc.android.ui.scan
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import cash.z.wallet.sdk.Synchronizer import cash.z.ecc.android.sdk.Synchronizer
import cash.z.wallet.sdk.ext.twig import cash.z.ecc.android.sdk.ext.twig
import javax.inject.Inject import javax.inject.Inject
class ScanViewModel @Inject constructor() : ViewModel() { class ScanViewModel @Inject constructor() : ViewModel() {

View File

@ -16,9 +16,10 @@ import cash.z.ecc.android.feedback.Report
import cash.z.ecc.android.feedback.Report.Funnel.Send import cash.z.ecc.android.feedback.Report.Funnel.Send
import cash.z.ecc.android.feedback.Report.Tap.* import cash.z.ecc.android.feedback.Report.Tap.*
import cash.z.ecc.android.ui.base.BaseFragment import cash.z.ecc.android.ui.base.BaseFragment
import cash.z.wallet.sdk.Synchronizer import cash.z.ecc.android.sdk.Synchronizer
import cash.z.wallet.sdk.block.CompactBlockProcessor.WalletBalance import cash.z.ecc.android.sdk.block.CompactBlockProcessor.WalletBalance
import cash.z.wallet.sdk.ext.* import cash.z.ecc.android.sdk.ext.*
import cash.z.ecc.android.sdk.validate.AddressType
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -85,9 +86,9 @@ class SendAddressFragment : BaseFragment<FragmentSendAddressBinding>(),
private fun onAddressChanged(address: String) { private fun onAddressChanged(address: String) {
resumedScope.launch { resumedScope.launch {
var type = when (sendViewModel.validateAddress(address)) { var type = when (sendViewModel.validateAddress(address)) {
is Synchronizer.AddressType.Transparent -> "This is a valid transparent address" to R.color.zcashGreen is AddressType.Transparent -> "This is a valid transparent address" to R.color.zcashGreen
is Synchronizer.AddressType.Shielded -> "This is a valid shielded address" to R.color.zcashGreen is AddressType.Shielded -> "This is a valid shielded address" to R.color.zcashGreen
is Synchronizer.AddressType.Invalid -> "This address appears to be invalid" to R.color.zcashRed is AddressType.Invalid -> "This address appears to be invalid" to R.color.zcashRed
} }
if (address == sendViewModel.synchronizer.getAddress()) type = if (address == sendViewModel.synchronizer.getAddress()) type =
"Warning, this appears to be your address!" to R.color.zcashRed "Warning, this appears to be your address!" to R.color.zcashRed

View File

@ -13,8 +13,8 @@ import cash.z.ecc.android.feedback.Report
import cash.z.ecc.android.feedback.Report.Funnel.Send import cash.z.ecc.android.feedback.Report.Funnel.Send
import cash.z.ecc.android.feedback.Report.Tap.* import cash.z.ecc.android.feedback.Report.Tap.*
import cash.z.ecc.android.ui.base.BaseFragment import cash.z.ecc.android.ui.base.BaseFragment
import cash.z.wallet.sdk.ext.toAbbreviatedAddress import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress
import cash.z.wallet.sdk.ext.convertZatoshiToZecString import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class SendConfirmFragment : BaseFragment<FragmentSendConfirmBinding>() { class SendConfirmFragment : BaseFragment<FragmentSendConfirmBinding>() {

View File

@ -12,11 +12,10 @@ import cash.z.ecc.android.ext.goneIf
import cash.z.ecc.android.feedback.Report import cash.z.ecc.android.feedback.Report
import cash.z.ecc.android.feedback.Report.Tap.* import cash.z.ecc.android.feedback.Report.Tap.*
import cash.z.ecc.android.ui.base.BaseFragment import cash.z.ecc.android.ui.base.BaseFragment
import cash.z.wallet.sdk.entity.* import cash.z.ecc.android.sdk.db.entity.*
import cash.z.wallet.sdk.ext.convertZatoshiToZecString import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString
import cash.z.wallet.sdk.ext.toAbbreviatedAddress import cash.z.ecc.android.sdk.ext.toAbbreviatedAddress
import cash.z.wallet.sdk.ext.twig import cash.z.ecc.android.sdk.ext.twig
import com.crashlytics.android.Crashlytics
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
@ -112,8 +111,8 @@ class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
} catch(t: Throwable) { } catch(t: Throwable) {
val message = "ERROR: error while handling pending transaction update! $t" val message = "ERROR: error while handling pending transaction update! $t"
twig(message) twig(message)
Crashlytics.log(message) mainActivity?.feedback?.report(Report.Error.NonFatal.TxUpdateFailed(t))
Crashlytics.logException(t) mainActivity?.feedback?.report(t)
} }
} }

View File

@ -14,13 +14,13 @@ import cash.z.ecc.android.feedback.Report.MetricType.*
import cash.z.ecc.android.lockbox.LockBox import cash.z.ecc.android.lockbox.LockBox
import cash.z.ecc.android.ui.setup.WalletSetupViewModel import cash.z.ecc.android.ui.setup.WalletSetupViewModel
import cash.z.ecc.android.ui.util.INCLUDE_MEMO_PREFIX import cash.z.ecc.android.ui.util.INCLUDE_MEMO_PREFIX
import cash.z.wallet.sdk.Initializer import cash.z.ecc.android.sdk.Initializer
import cash.z.wallet.sdk.Synchronizer import cash.z.ecc.android.sdk.Synchronizer
import cash.z.wallet.sdk.entity.* import cash.z.ecc.android.sdk.db.entity.*
import cash.z.wallet.sdk.ext.ZcashSdk import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.wallet.sdk.ext.convertZatoshiToZecString import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString
import cash.z.wallet.sdk.ext.twig import cash.z.ecc.android.sdk.ext.twig
import com.crashlytics.android.Crashlytics import cash.z.ecc.android.sdk.validate.AddressType
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
@ -94,7 +94,7 @@ class SendViewModel @Inject constructor() : ViewModel() {
} }
} }
suspend fun validateAddress(address: String): Synchronizer.AddressType = suspend fun validateAddress(address: String): AddressType =
synchronizer.validateAddress(address) synchronizer.validateAddress(address)
fun validate(maxZatoshi: Long?) = flow<String?> { fun validate(maxZatoshi: Long?) = flow<String?> {
@ -143,7 +143,7 @@ class SendViewModel @Inject constructor() : ViewModel() {
report(metricId) report(metricId)
} }
} catch (t: Throwable) { } catch (t: Throwable) {
Crashlytics.logException(RuntimeException("Error while updating Metrics", t)) feedback.report(t)
} }
} }

View File

@ -10,11 +10,9 @@ import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.activity.addCallback import androidx.activity.addCallback
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import cash.z.ecc.android.R
import cash.z.ecc.android.ZcashWalletApp import cash.z.ecc.android.ZcashWalletApp
import cash.z.ecc.android.databinding.FragmentBackupBinding import cash.z.ecc.android.databinding.FragmentBackupBinding
import cash.z.ecc.android.di.viewmodel.activityViewModel import cash.z.ecc.android.di.viewmodel.activityViewModel
import cash.z.ecc.android.di.viewmodel.viewModel
import cash.z.ecc.android.feedback.Report import cash.z.ecc.android.feedback.Report
import cash.z.ecc.android.feedback.Report.MetricType.SEED_PHRASE_LOADED import cash.z.ecc.android.feedback.Report.MetricType.SEED_PHRASE_LOADED
import cash.z.ecc.android.feedback.Report.Tap.BACKUP_DONE import cash.z.ecc.android.feedback.Report.Tap.BACKUP_DONE

View File

@ -18,7 +18,7 @@ import cash.z.ecc.android.feedback.Report.Tap.*
import cash.z.ecc.android.ui.base.BaseFragment import cash.z.ecc.android.ui.base.BaseFragment
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.SEED_WITHOUT_BACKUP import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.SEED_WITHOUT_BACKUP
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.SEED_WITH_BACKUP import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.SEED_WITH_BACKUP
import cash.z.wallet.sdk.Initializer import cash.z.ecc.android.sdk.Initializer
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach

View File

@ -17,12 +17,13 @@ import cash.z.ecc.android.R
import cash.z.ecc.android.databinding.FragmentRestoreBinding import cash.z.ecc.android.databinding.FragmentRestoreBinding
import cash.z.ecc.android.di.viewmodel.activityViewModel import cash.z.ecc.android.di.viewmodel.activityViewModel
import cash.z.ecc.android.ext.goneIf import cash.z.ecc.android.ext.goneIf
import cash.z.ecc.android.ext.showInvalidSeedPhraseError
import cash.z.ecc.android.feedback.Report import cash.z.ecc.android.feedback.Report
import cash.z.ecc.android.feedback.Report.Funnel.Restore import cash.z.ecc.android.feedback.Report.Funnel.Restore
import cash.z.ecc.android.feedback.Report.Tap.* import cash.z.ecc.android.feedback.Report.Tap.*
import cash.z.ecc.android.ui.base.BaseFragment import cash.z.ecc.android.ui.base.BaseFragment
import cash.z.wallet.sdk.ext.ZcashSdk import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.wallet.sdk.ext.twig import cash.z.ecc.android.sdk.ext.twig
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.tylersuehr.chips.Chip import com.tylersuehr.chips.Chip
import com.tylersuehr.chips.ChipsAdapter import com.tylersuehr.chips.ChipsAdapter
@ -118,7 +119,12 @@ class RestoreFragment : BaseFragment<FragmentRestoreBinding>(), View.OnKeyListen
if (birthdateString.isNullOrEmpty()) ZcashSdk.SAPLING_ACTIVATION_HEIGHT else birthdateString.toInt() if (birthdateString.isNullOrEmpty()) ZcashSdk.SAPLING_ACTIVATION_HEIGHT else birthdateString.toInt()
}.coerceAtLeast(ZcashSdk.SAPLING_ACTIVATION_HEIGHT) }.coerceAtLeast(ZcashSdk.SAPLING_ACTIVATION_HEIGHT)
importWallet(seedPhrase, birthday) try {
walletSetup.validatePhrase(seedPhrase)
importWallet(seedPhrase, birthday)
} catch (t: Throwable) {
mainActivity?.showInvalidSeedPhraseError(t)
}
} }
private fun importWallet(seedPhrase: String, birthday: Int) { private fun importWallet(seedPhrase: String, birthday: Int) {

View File

@ -11,7 +11,7 @@ import cash.z.ecc.android.feedback.Report
import cash.z.ecc.android.feedback.Report.Funnel.Restore import cash.z.ecc.android.feedback.Report.Funnel.Restore
import cash.z.ecc.android.ui.MainActivity import cash.z.ecc.android.ui.MainActivity
import cash.z.ecc.android.ui.setup.SeedWordChip import cash.z.ecc.android.ui.setup.SeedWordChip
import cash.z.wallet.sdk.ext.twig import cash.z.ecc.android.sdk.ext.twig
class SeedWordAdapter : ChipsAdapter { class SeedWordAdapter : ChipsAdapter {

View File

@ -8,11 +8,11 @@ import cash.z.ecc.android.feedback.measure
import cash.z.ecc.android.lockbox.LockBox import cash.z.ecc.android.lockbox.LockBox
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.* import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.*
import cash.z.ecc.kotlin.mnemonic.Mnemonics import cash.z.ecc.kotlin.mnemonic.Mnemonics
import cash.z.wallet.sdk.Initializer import cash.z.ecc.android.sdk.Initializer
import cash.z.wallet.sdk.Initializer.DefaultBirthdayStore import cash.z.ecc.android.sdk.Initializer.DefaultBirthdayStore
import cash.z.wallet.sdk.Initializer.DefaultBirthdayStore.Companion.ImportedWalletBirthdayStore import cash.z.ecc.android.sdk.Initializer.DefaultBirthdayStore.Companion.ImportedWalletBirthdayStore
import cash.z.wallet.sdk.Initializer.DefaultBirthdayStore.Companion.NewWalletBirthdayStore import cash.z.ecc.android.sdk.Initializer.DefaultBirthdayStore.Companion.NewWalletBirthdayStore
import cash.z.wallet.sdk.ext.twig import cash.z.ecc.android.sdk.ext.twig
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
@ -142,6 +142,13 @@ class WalletSetupViewModel @Inject constructor() : ViewModel() {
} }
} }
/**
* Throw an exception if the seed phrase is bad.
*/
fun validatePhrase(seedPhrase: String) {
mnemonics.validate(seedPhrase.toCharArray())
}
object LockBoxKey { object LockBoxKey {
const val SEED = "cash.z.ecc.android.SEED" const val SEED = "cash.z.ecc.android.SEED"
const val SEED_PHRASE = "cash.z.ecc.android.SEED_PHRASE" const val SEED_PHRASE = "cash.z.ecc.android.SEED_PHRASE"

View File

@ -2,7 +2,7 @@ package cash.z.ecc.android
import cash.z.ecc.android.feedback.Feedback import cash.z.ecc.android.feedback.Feedback
import cash.z.ecc.android.ui.send.SendViewModel import cash.z.ecc.android.ui.send.SendViewModel
import cash.z.wallet.sdk.entity.* import cash.z.ecc.android.sdk.entity.*
import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyZeroInteractions import com.nhaarman.mockitokotlin2.verifyZeroInteractions
import com.nhaarman.mockitokotlin2.whenever import com.nhaarman.mockitokotlin2.whenever

View File

@ -1,9 +1,6 @@
import cash.z.ecc.android.Deps import cash.z.ecc.android.Deps
buildscript { buildscript {
ext {
kotlin_version = '1.3.61'
}
repositories { repositories {
google() google()
jcenter() jcenter()
@ -12,11 +9,11 @@ buildscript {
} }
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.6.0-rc02' classpath 'com.android.tools.build:gradle:4.0.0'
classpath 'com.google.gms:google-services:4.3.3' classpath 'com.google.gms:google-services:4.3.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${Deps.kotlinVersion}" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${Deps.kotlinVersion}"
classpath 'io.fabric.tools:gradle:1.31.2' classpath 'io.fabric.tools:gradle:1.31.2'
classpath 'com.google.firebase:perf-plugin:1.3.1' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.1.1'
} }
} }

View File

@ -3,7 +3,7 @@ package cash.z.ecc.android
object Deps { object Deps {
// For use in the top-level build.gradle which gives an error when provided // For use in the top-level build.gradle which gives an error when provided
// `Deps.Kotlin.version` directly // `Deps.Kotlin.version` directly
const val kotlinVersion = "1.3.61" const val kotlinVersion = "1.3.72"
const val compileSdkVersion = 29 const val compileSdkVersion = 29
const val buildToolsVersion = "29.0.2" const val buildToolsVersion = "29.0.2"
@ -11,18 +11,34 @@ object Deps {
const val targetSdkVersion = 29 const val targetSdkVersion = 29
object AndroidX { object AndroidX {
const val ANNOTATION = "androidx.annotation:annotation:1.1.0"
const val APPCOMPAT = "androidx.appcompat:appcompat:1.1.0" const val APPCOMPAT = "androidx.appcompat:appcompat:1.1.0"
const val CORE_KTX = "androidx.core:core-ktx:1.1.0"
const val CONSTRAINT_LAYOUT = "androidx.constraintlayout:constraintlayout:1.1.3" const val CONSTRAINT_LAYOUT = "androidx.constraintlayout:constraintlayout:1.1.3"
const val CORE_KTX = "androidx.core:core-ktx:1.1.0"
const val FRAGMENT_KTX = "androidx.fragment:fragment-ktx:1.1.0-beta01" const val FRAGMENT_KTX = "androidx.fragment:fragment-ktx:1.1.0-beta01"
const val LEGACY = "androidx.legacy:legacy-support-v4:1.0.0"
const val MULTIDEX = "androidx.multidex:multidex:2.0.1" const val MULTIDEX = "androidx.multidex:multidex:2.0.1"
const val PAGING = "androidx.paging:paging-runtime-ktx:2.1.2"
object CameraX : Version("1.0.0-beta04") {
val CAMERA2 = "androidx.camera:camera-camera2:1.0.0-beta04"
val CORE = "androidx.camera:camera-core:1.0.0-beta04"
val LIFECYCLE = "androidx.camera:camera-lifecycle:1.0.0-beta04"
object View : Version("1.0.0-alpha11") {
val EXT = "androidx.camera:camera-extensions:1.0.0-alpha11"
val VIEW = "androidx.camera:camera-view:1.0.0-alpha11"
}
}
object Lifecycle : Version("2.2.0-rc02") {
val LIFECYCLE_RUNTIME_KTX = "androidx.lifecycle:lifecycle-runtime-ktx:$version"
val LIFECYCLE_EXTENSIONS = "androidx.lifecycle:lifecycle-extensions:$version"
}
object Navigation : Version("2.2.0") { object Navigation : Version("2.2.0") {
val FRAGMENT_KTX = "androidx.navigation:navigation-fragment-ktx:$version" val FRAGMENT_KTX = "androidx.navigation:navigation-fragment-ktx:$version"
val UI_KTX = "androidx.navigation:navigation-ui-ktx:$version" val UI_KTX = "androidx.navigation:navigation-ui-ktx:$version"
} }
object Lifecycle: Version("2.2.0-rc02") { object Room : Version("2.2.5") {
val LIFECYCLE_RUNTIME_KTX = "androidx.lifecycle:lifecycle-runtime-ktx:$version" val ROOM_COMPILER = "androidx.room:room-compiler:$version"
val LIFECYCLE_EXTENSIONS = "androidx.lifecycle:lifecycle-extensions:$version" val ROOM_KTX = "androidx.room:room-ktx:$version"
} }
} }
object Dagger : Version("2.25.2") { object Dagger : Version("2.25.2") {
@ -31,35 +47,58 @@ object Deps {
val COMPILER = "com.google.dagger:dagger-compiler:$version" val COMPILER = "com.google.dagger:dagger-compiler:$version"
} }
object Google { object Google {
// solves error: Duplicate class com.google.common.util.concurrent.ListenableFuture found in modules jetified-guava-26.0-android.jar (com.google.guava:guava:26.0-android) and listenablefuture-1.0.jar (com.google.guava:listenablefuture:1.0)
// per this recommendation from Chris Povirk, given guava's decision to split ListenableFuture away from Guava: https://groups.google.com/d/msg/guava-discuss/GghaKwusjcY/bCIAKfzOEwAJ
const val GUAVA = "com.google.guava:guava:27.0.1-android"
const val MATERIAL = "com.google.android.material:material:1.1.0-beta01" const val MATERIAL = "com.google.android.material:material:1.1.0-beta01"
// QR Scanner
const val ML_VISION = "com.google.firebase:firebase-ml-vision:24.0.3"
}
object Grpc : Version("1.25.0") {
val ANDROID = "io.grpc:grpc-android:$version"
val OKHTTP = "io.grpc:grpc-okhttp:$version"
val PROTOBUG = "io.grpc:grpc-protobuf-lite:$version"
val STUB = "io.grpc:grpc-stub:$version"
}
object Analytics { // for dogfooding/crash-reporting/feedback only on internal team builds
val CRASHLYTICS = "com.google.firebase:firebase-crashlytics:17.0.1"
val MIXPANEL = "com.mixpanel.android:mixpanel-android:5.6.3"
} }
object JavaX { object JavaX {
const val INJECT = "javax.inject:javax.inject:1" const val INJECT = "javax.inject:javax.inject:1"
const val JAVA_ANNOTATION = "javax.annotation:javax.annotation-api:1.3.2"
} }
object Kotlin : Version(kotlinVersion) { object Kotlin : Version(kotlinVersion) {
val STDLIB = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$version" val STDLIB = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$version"
object Coroutines : Version("1.3.2") { object Coroutines : Version("1.3.2") {
val ANDROID = "org.jetbrains.kotlinx:kotlinx-coroutines-android:$version" val ANDROID = "org.jetbrains.kotlinx:kotlinx-coroutines-android:$version"
val CORE = "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version" val CORE = "org.jetbrains.kotlinx:kotlinx-coroutines-core:$version"
val TEST = "org.jetbrains.kotlinx:kotlinx-coroutines-test:$version" val TEST = "org.jetbrains.kotlinx:kotlinx-coroutines-test:$version"
} }
} }
object Zcash { object Zcash {
val ANDROID_WALLET_PLUGINS = "com.github.zcash:zcash-android-wallet-plugins:1.0.1" const val ANDROID_WALLET_PLUGINS = "cash.z.ecc.android:zcash-android-wallet-plugins:1.0.0"
const val KOTLIN_BIP39 = "cash.z.ecc.android:kotlin-bip39:1.0.0-beta08"
object Sdk : Version("1.1.0-beta01") {
val MAINNET = "cash.z.ecc.android:sdk-mainnet:$version"
val TESTNET = "cash.z.ecc.android:sdk-testnet:$version"
}
} }
object Misc { object Misc {
const val LOTTIE = "com.airbnb.android:lottie:3.1.0"
object Plugins { object Plugins {
val SECURE_STORAGE = "de.adorsys.android:securestoragelibrary:1.2.2" const val SECURE_STORAGE = "de.adorsys.android:securestoragelibrary:1.2.2"
val ANDROID_BIP39 = "cash.z.ecc.android:android-bip39:1.0.0-beta07" const val QR_SCANNER = "com.google.zxing:core:3.2.1"
val QR_SCANNER = "com.google.zxing:core:3.2.1"
} }
} }
object Test { object Test {
const val JUNIT = "junit:junit:4.12" const val JUNIT = "junit:junit:4.12"
const val MOKITO = "junit:junit:4.12"
const val COROUTINES_TEST = "junit:junit:4.12"
object Android { object Android {
const val JUNIT = "androidx.test.ext:junit:1.1.1" const val JUNIT = "androidx.test.ext:junit:1.1.1"
const val ESPRESSO = "androidx.test.espresso:espresso-core:3.2.0" const val ESPRESSO = "androidx.test.espresso:espresso-core:3.2.0"
} }
} }
} }

View File

@ -125,6 +125,7 @@ class FeedbackCoordinator(val feedback: Feedback, defaultObservers: Set<Feedback
} }
companion object { companion object {
const val ENABLED = "setting.feedbackcoordinater.enabled"
private val mutex: Mutex = Mutex() private val mutex: Mutex = Mutex()
} }
} }

View File

@ -1,6 +1,6 @@
#Tue Jan 07 12:00:21 EST 2020 #Fri May 29 19:00:53 EDT 2020
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip

View File

@ -8,7 +8,7 @@ dependencies {
// Zcash // Zcash
implementation Deps.Zcash.ANDROID_WALLET_PLUGINS implementation Deps.Zcash.ANDROID_WALLET_PLUGINS
implementation Deps.Misc.Plugins.ANDROID_BIP39 implementation Deps.Zcash.KOTLIN_BIP39
testImplementation Deps.Test.JUNIT testImplementation Deps.Test.JUNIT
} }

View File

@ -1,57 +1,26 @@
package cash.z.ecc.kotlin.mnemonic package cash.z.ecc.kotlin.mnemonic
import cash.z.android.plugin.MnemonicPlugin import cash.z.android.plugin.MnemonicPlugin
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.bip39.Mnemonics.MnemonicCode import cash.z.ecc.android.bip39.Mnemonics.MnemonicCode
import cash.z.ecc.android.bip39.Mnemonics.WordCount import cash.z.ecc.android.bip39.Mnemonics.WordCount
import cash.z.ecc.android.bip39.toEntropy import cash.z.ecc.android.bip39.toEntropy
import cash.z.ecc.android.bip39.toSeed import cash.z.ecc.android.bip39.toSeed
import java.util.* import java.util.*
import java.util.Locale.ENGLISH
import javax.inject.Inject import javax.inject.Inject
class Mnemonics @Inject constructor(): MnemonicPlugin { class Mnemonics @Inject constructor() : MnemonicPlugin {
override fun fullWordList(languageCode: String): List<String> { override fun fullWordList(languageCode: String) = Mnemonics.getCachedWords(Locale.ENGLISH.language)
return cash.z.ecc.android.bip39.Mnemonics.getCachedWords(Locale.ENGLISH.language) override fun nextEntropy(): ByteArray = WordCount.COUNT_24.toEntropy()
} override fun nextMnemonic(): CharArray = MnemonicCode(WordCount.COUNT_24).chars
override fun nextMnemonic(entropy: ByteArray): CharArray = MnemonicCode(entropy).chars
override fun nextMnemonicList(): List<CharArray> = MnemonicCode(WordCount.COUNT_24).words
override fun nextMnemonicList(entropy: ByteArray): List<CharArray> = MnemonicCode(entropy).words
override fun toSeed(mnemonic: CharArray): ByteArray = MnemonicCode(mnemonic).toSeed()
override fun toWordList(mnemonic: CharArray): List<CharArray> = MnemonicCode(mnemonic).words
override fun nextEntropy(): ByteArray { fun validate(mnemonic: CharArray) {
return WordCount.COUNT_24.toEntropy() MnemonicCode(mnemonic).validate()
}
override fun nextMnemonic(): CharArray {
return nextMnemonic(nextEntropy())
}
override fun nextMnemonic(entropy: ByteArray): CharArray {
return MnemonicCode(entropy).chars
}
override fun nextMnemonicList(): List<CharArray> {
return nextMnemonicList(nextEntropy())
}
override fun nextMnemonicList(entropy: ByteArray): List<CharArray> {
return MnemonicCode(entropy).map { it.toCharArray() }
}
override fun toSeed(mnemonic: CharArray): ByteArray {
return MnemonicCode(mnemonic).toSeed()
}
override fun toWordList(mnemonic: CharArray): List<CharArray> {
val wordList = mutableListOf<CharArray>()
var cursor = 0
repeat(mnemonic.size) { i ->
val isSpace = mnemonic[i] == ' '
if (isSpace || i == (mnemonic.size - 1)) {
val wordSize = i - cursor + if (isSpace) 0 else 1
wordList.add(CharArray(wordSize).apply {
repeat(wordSize) {
this[it] = mnemonic[cursor + it]
}
})
cursor = i + 1
}
}
return wordList
} }
} }

View File

@ -1,4 +1,3 @@
rootProject.name='Zcash Wallet' rootProject.name='Zcash Wallet'
include ':app', ':qrecycler', ':feedback', ':mnemonic', ':lockbox', ':sdk', ':chipsinputlayout' include ':app', ':qrecycler', ':feedback', ':mnemonic', ':lockbox', ':chipsinputlayout'
project(":sdk").projectDir = file("../zcash-android-wallet-sdk")
project(":chipsinputlayout").projectDir = file("../../clones/chips-input-layout/library") project(":chipsinputlayout").projectDir = file("../../clones/chips-input-layout/library")