commit
4ecac12f03
|
@ -72,7 +72,13 @@ android {
|
|||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
|
||||
kapt {
|
||||
arguments {
|
||||
arg 'dagger.fastInit', 'enabled'
|
||||
arg 'dagger.fullBindingGraphValidation', 'ERROR'
|
||||
arg 'dagger.gradle.incremental'
|
||||
}
|
||||
}
|
||||
applicationVariants.all { variant ->
|
||||
variant.outputs.all {
|
||||
outputFileName = "$archivesBaseName-v${defaultConfig.versionName}-${variant.buildType.name}.apk"
|
||||
|
@ -96,6 +102,7 @@ dependencies {
|
|||
implementation Deps.AndroidX.CORE_KTX
|
||||
implementation Deps.AndroidX.CONSTRAINT_LAYOUT
|
||||
implementation Deps.AndroidX.Lifecycle.LIFECYCLE_RUNTIME_KTX
|
||||
implementation Deps.AndroidX.Lifecycle.LIFECYCLE_EXTENSIONS
|
||||
implementation Deps.AndroidX.Navigation.FRAGMENT_KTX
|
||||
implementation Deps.AndroidX.Navigation.UI_KTX
|
||||
implementation "androidx.room:room-ktx:2.2.3"
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
package cash.z.ecc.android
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import cash.z.ecc.android.di.DaggerAppComponent
|
||||
import cash.z.ecc.android.feedback.FeedbackCoordinator
|
||||
import cash.z.ecc.android.di.component.AppComponent
|
||||
import cash.z.ecc.android.di.component.DaggerAppComponent
|
||||
import cash.z.wallet.sdk.ext.TroubleshootingTwig
|
||||
import cash.z.wallet.sdk.ext.Twig
|
||||
import cash.z.wallet.sdk.ext.twig
|
||||
import dagger.android.AndroidInjector
|
||||
import dagger.android.DaggerApplication
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
class ZcashWalletApp : DaggerApplication() {
|
||||
class ZcashWalletApp : Application() {
|
||||
|
||||
var creationTime: Long = 0
|
||||
private set
|
||||
|
@ -25,17 +23,11 @@ class ZcashWalletApp : DaggerApplication() {
|
|||
// Setup handler for uncaught exceptions.
|
||||
super.onCreate()
|
||||
|
||||
component = DaggerAppComponent.factory().create(this)
|
||||
Thread.setDefaultUncaughtExceptionHandler(ExceptionReporter(Thread.getDefaultUncaughtExceptionHandler()))
|
||||
Twig.plant(TroubleshootingTwig())
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement the HasActivityInjector behavior so that dagger knows which [AndroidInjector] to use.
|
||||
*/
|
||||
override fun applicationInjector(): AndroidInjector<out DaggerApplication> {
|
||||
return DaggerAppComponent.factory().create(this)
|
||||
}
|
||||
|
||||
override fun attachBaseContext(base: Context) {
|
||||
super.attachBaseContext(base)
|
||||
// MultiDex.install(this)
|
||||
|
@ -43,6 +35,7 @@ class ZcashWalletApp : DaggerApplication() {
|
|||
|
||||
companion object {
|
||||
lateinit var instance: ZcashWalletApp
|
||||
lateinit var component: AppComponent
|
||||
}
|
||||
|
||||
class ExceptionReporter(val ogHandler: Thread.UncaughtExceptionHandler) : Thread.UncaughtExceptionHandler {
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
package cash.z.ecc.android.di
|
||||
|
||||
import dagger.Module
|
||||
|
||||
@Module
|
||||
abstract class AppBindingModule {
|
||||
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
package cash.z.ecc.android.di
|
||||
|
||||
import cash.z.ecc.android.ZcashWalletApp
|
||||
import cash.z.ecc.android.ui.MainActivityModule
|
||||
import cash.z.ecc.android.ui.detail.WalletDetailFragmentModule
|
||||
import cash.z.ecc.android.ui.home.HomeFragmentModule
|
||||
import cash.z.ecc.android.ui.receive.ReceiveFragmentModule
|
||||
import cash.z.ecc.android.ui.send.*
|
||||
import cash.z.ecc.android.ui.setup.BackupFragmentModule
|
||||
import cash.z.ecc.android.ui.setup.LandingFragmentModule
|
||||
import dagger.BindsInstance
|
||||
import dagger.Component
|
||||
import dagger.android.AndroidInjector
|
||||
import dagger.android.support.AndroidSupportInjectionModule
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@Component(
|
||||
modules = [
|
||||
AndroidSupportInjectionModule::class,
|
||||
|
||||
AppModule::class,
|
||||
|
||||
// Activities
|
||||
MainActivityModule::class,
|
||||
|
||||
// Fragments
|
||||
HomeFragmentModule::class,
|
||||
ReceiveFragmentModule::class,
|
||||
SendAddressFragmentModule::class,
|
||||
SendMemoFragmentModule::class,
|
||||
SendConfirmFragmentModule::class,
|
||||
SendFinalFragmentModule::class,
|
||||
WalletDetailFragmentModule::class,
|
||||
LandingFragmentModule::class,
|
||||
BackupFragmentModule::class
|
||||
]
|
||||
)
|
||||
interface AppComponent : AndroidInjector<ZcashWalletApp> {
|
||||
|
||||
@Component.Factory
|
||||
interface Factory {
|
||||
fun create(@BindsInstance application: ZcashWalletApp): AppComponent
|
||||
}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package cash.z.ecc.android.di
|
||||
|
||||
import android.content.Context
|
||||
import cash.z.ecc.android.ZcashWalletApp
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module(includes = [AppBindingModule::class, ViewModelModule::class])
|
||||
class AppModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAppContext(): Context = ZcashWalletApp.instance
|
||||
}
|
|
@ -1,52 +0,0 @@
|
|||
package cash.z.ecc.android.di
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import cash.z.ecc.android.di.annotation.ViewModelKey
|
||||
import cash.z.ecc.android.ui.home.HomeViewModel
|
||||
import cash.z.ecc.android.ui.send.SendViewModel
|
||||
import cash.z.ecc.android.ui.setup.WalletSetupViewModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.multibindings.IntoMap
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
abstract class ViewModelModule {
|
||||
|
||||
@Binds
|
||||
abstract fun bindViewModelFactory(implementation: ViewModelFactory): ViewModelProvider.Factory
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(WalletSetupViewModel::class)
|
||||
abstract fun bindWalletSetupViewModel(implementation: WalletSetupViewModel): ViewModel
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(HomeViewModel::class)
|
||||
abstract fun bindHomeViewModel(implementation: HomeViewModel): ViewModel
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(SendViewModel::class)
|
||||
abstract fun bindSendViewModel(implementation: SendViewModel): ViewModel
|
||||
}
|
||||
|
||||
@Singleton
|
||||
class ViewModelFactory @Inject constructor(
|
||||
private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
|
||||
) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
val creator = creators[modelClass] ?: creators.entries.firstOrNull {
|
||||
modelClass.isAssignableFrom(it.key)
|
||||
}?.value ?: throw IllegalArgumentException(
|
||||
"No map entry found for ${modelClass.canonicalName}." +
|
||||
" Verify that this ViewModel has been added to the ViewModelModule."
|
||||
)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return creator.get() as T
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package cash.z.ecc.android.di.annotation
|
||||
|
||||
import javax.inject.Scope
|
||||
|
||||
@Scope
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class SynchronizerScope
|
|
@ -0,0 +1,21 @@
|
|||
package cash.z.ecc.android.di.component
|
||||
|
||||
import cash.z.ecc.android.ZcashWalletApp
|
||||
import cash.z.ecc.android.di.module.AppModule
|
||||
import dagger.BindsInstance
|
||||
import dagger.Component
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@Component(modules = [AppModule::class])
|
||||
interface AppComponent {
|
||||
// Subcomponents
|
||||
fun mainActivitySubcomponent(): MainActivitySubcomponent.Factory
|
||||
fun synchronizerSubcomponent(): SynchronizerSubcomponent.Factory
|
||||
fun initializerSubcomponent(): InitializerSubcomponent.Factory
|
||||
|
||||
@Component.Factory
|
||||
interface Factory {
|
||||
fun create(@BindsInstance application: ZcashWalletApp): AppComponent
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package cash.z.ecc.android.di.component
|
||||
|
||||
import cash.z.ecc.android.ZcashWalletApp
|
||||
import cash.z.ecc.android.di.annotation.ActivityScope
|
||||
import cash.z.ecc.android.di.annotation.SynchronizerScope
|
||||
import cash.z.ecc.android.di.module.InitializerModule
|
||||
import cash.z.wallet.sdk.Initializer
|
||||
import dagger.BindsInstance
|
||||
import dagger.Subcomponent
|
||||
|
||||
@SynchronizerScope
|
||||
@Subcomponent(modules = [InitializerModule::class])
|
||||
interface InitializerSubcomponent {
|
||||
|
||||
fun initializer(): Initializer
|
||||
fun birthdayStore(): Initializer.WalletBirthdayStore
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
fun create(@BindsInstance birthdayStore: Initializer.WalletBirthdayStore = Initializer.DefaultBirthdayStore(ZcashWalletApp.instance)): InitializerSubcomponent
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package cash.z.ecc.android.di.component
|
||||
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import cash.z.ecc.android.di.annotation.ActivityScope
|
||||
import cash.z.ecc.android.di.module.MainActivityModule
|
||||
import cash.z.ecc.android.ui.MainActivity
|
||||
import dagger.BindsInstance
|
||||
import dagger.Subcomponent
|
||||
import javax.inject.Named
|
||||
|
||||
@ActivityScope
|
||||
@Subcomponent(modules = [MainActivityModule::class])
|
||||
interface MainActivitySubcomponent {
|
||||
|
||||
fun inject(activity: MainActivity)
|
||||
|
||||
@Named("BeforeSynchronizer") fun viewModelFactory(): ViewModelProvider.Factory
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
fun create(@BindsInstance activity: FragmentActivity): MainActivitySubcomponent
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package cash.z.ecc.android.di.component
|
||||
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import cash.z.ecc.android.di.annotation.SynchronizerScope
|
||||
import cash.z.ecc.android.di.module.SynchronizerModule
|
||||
import cash.z.wallet.sdk.Initializer
|
||||
import cash.z.wallet.sdk.Synchronizer
|
||||
import dagger.BindsInstance
|
||||
import dagger.Subcomponent
|
||||
import javax.inject.Named
|
||||
|
||||
@SynchronizerScope
|
||||
@Subcomponent(modules = [SynchronizerModule::class])
|
||||
interface SynchronizerSubcomponent {
|
||||
|
||||
fun synchronizer(): Synchronizer
|
||||
|
||||
@Named("Synchronizer") fun viewModelFactory(): ViewModelProvider.Factory
|
||||
|
||||
@Subcomponent.Factory
|
||||
interface Factory {
|
||||
fun create(@BindsInstance initializer: Initializer): SynchronizerSubcomponent
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package cash.z.ecc.android.di.module
|
||||
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import cash.z.ecc.android.ZcashWalletApp
|
||||
import cash.z.ecc.android.di.component.InitializerSubcomponent
|
||||
import cash.z.ecc.android.di.component.MainActivitySubcomponent
|
||||
import cash.z.ecc.android.di.component.SynchronizerSubcomponent
|
||||
import cash.z.wallet.sdk.Initializer
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.Reusable
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module(subcomponents = [MainActivitySubcomponent::class])
|
||||
class AppModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAppContext(): Context = ZcashWalletApp.instance
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideClipboard(context: Context) =
|
||||
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package cash.z.ecc.android.di.module
|
||||
|
||||
import android.content.Context
|
||||
import cash.z.wallet.sdk.Initializer
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.Reusable
|
||||
|
||||
@Module
|
||||
class InitializerModule {
|
||||
private val host = "lightd-main.zecwallet.co"
|
||||
private val port = 443
|
||||
|
||||
@Provides
|
||||
@Reusable
|
||||
fun provideInitializer(appContext: Context) = Initializer(appContext, host, port)
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
package cash.z.ecc.android.di.module
|
||||
|
||||
import cash.z.ecc.android.di.annotation.ActivityScope
|
||||
import cash.z.ecc.android.di.component.InitializerSubcomponent
|
||||
import cash.z.ecc.android.di.component.SynchronizerSubcomponent
|
||||
import cash.z.ecc.android.feedback.*
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.multibindings.IntoSet
|
||||
|
||||
@Module(includes = [ViewModelsActivityModule::class], subcomponents = [SynchronizerSubcomponent::class, InitializerSubcomponent::class])
|
||||
class MainActivityModule {
|
||||
|
||||
@Provides
|
||||
@ActivityScope
|
||||
fun provideFeedback(): Feedback = Feedback()
|
||||
|
||||
@Provides
|
||||
@ActivityScope
|
||||
fun provideFeedbackCoordinator(
|
||||
feedback: Feedback,
|
||||
defaultObservers: Set<@JvmSuppressWildcards FeedbackCoordinator.FeedbackObserver>
|
||||
): FeedbackCoordinator = FeedbackCoordinator(feedback, defaultObservers)
|
||||
|
||||
|
||||
//
|
||||
// Default Feedback Observer Set
|
||||
//
|
||||
|
||||
@Provides
|
||||
@ActivityScope
|
||||
@IntoSet
|
||||
fun provideFeedbackFile(): FeedbackCoordinator.FeedbackObserver = FeedbackFile()
|
||||
|
||||
@Provides
|
||||
@ActivityScope
|
||||
@IntoSet
|
||||
fun provideFeedbackConsole(): FeedbackCoordinator.FeedbackObserver = FeedbackConsole()
|
||||
|
||||
@Provides
|
||||
@ActivityScope
|
||||
@IntoSet
|
||||
fun provideFeedbackMixpanel(): FeedbackCoordinator.FeedbackObserver = FeedbackMixpanel()
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package cash.z.ecc.android.di.module
|
||||
|
||||
import android.content.Context
|
||||
import cash.z.ecc.android.di.annotation.SynchronizerScope
|
||||
import cash.z.wallet.sdk.Initializer
|
||||
import cash.z.wallet.sdk.Synchronizer
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
||||
/**
|
||||
* Module that creates the synchronizer from an initializer and also everything that depends on the
|
||||
* synchronizer (because it doesn't exist prior to this module being installed).
|
||||
*/
|
||||
@Module(includes = [ViewModelsSynchronizerModule::class])
|
||||
class SynchronizerModule {
|
||||
|
||||
@Provides
|
||||
@SynchronizerScope
|
||||
fun provideSynchronizer(appContext: Context, initializer: Initializer): Synchronizer {
|
||||
return Synchronizer(appContext, initializer)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
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
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.multibindings.IntoMap
|
||||
import javax.inject.Named
|
||||
|
||||
/**
|
||||
* View model related objects, scoped to the activity that do not depend on the Synchronizer. These
|
||||
* are any VMs that must be created before the Synchronizer.
|
||||
*/
|
||||
@Module
|
||||
abstract class ViewModelsActivityModule {
|
||||
|
||||
@ActivityScope
|
||||
@Binds
|
||||
@IntoMap
|
||||
@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
|
||||
* to both the parent and the child components. If they all live in the child component, which
|
||||
* isn't created until the synchronizer exists, then the parent component will not have the
|
||||
* view models yet.
|
||||
*/
|
||||
@ActivityScope
|
||||
@Named("BeforeSynchronizer")
|
||||
@Binds
|
||||
abstract fun bindViewModelFactory(viewModelFactory: ViewModelFactory): ViewModelProvider.Factory
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package cash.z.ecc.android.di.module
|
||||
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
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.detail.WalletDetailViewModel
|
||||
import cash.z.ecc.android.ui.home.HomeViewModel
|
||||
import cash.z.ecc.android.ui.receive.ReceiveViewModel
|
||||
import cash.z.ecc.android.ui.send.SendViewModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.multibindings.IntoMap
|
||||
import javax.inject.Named
|
||||
|
||||
/**
|
||||
* View model related objects, scoped to the synchronizer.
|
||||
*/
|
||||
@Module
|
||||
abstract class ViewModelsSynchronizerModule {
|
||||
@SynchronizerScope
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(HomeViewModel::class)
|
||||
abstract fun bindHomeViewModel(implementation: HomeViewModel): ViewModel
|
||||
|
||||
@SynchronizerScope
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(SendViewModel::class)
|
||||
abstract fun bindSendViewModel(implementation: SendViewModel): ViewModel
|
||||
|
||||
@SynchronizerScope
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(WalletDetailViewModel::class)
|
||||
abstract fun bindWalletDetailViewModel(implementation: WalletDetailViewModel): ViewModel
|
||||
|
||||
@SynchronizerScope
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ViewModelKey(ReceiveViewModel::class)
|
||||
abstract fun bindReceiveViewModel(implementation: ReceiveViewModel): ViewModel
|
||||
|
||||
|
||||
/**
|
||||
* Factory for view models that are not created until the Synchronizer exists. Only VMs that
|
||||
* require the Synchronizer should wait until it is created. In other words, these are the VMs
|
||||
* that live within the scope of the Synchronizer.
|
||||
*/
|
||||
@SynchronizerScope
|
||||
@Named("Synchronizer")
|
||||
@Binds
|
||||
abstract fun bindViewModelFactory(viewModelFactory: ViewModelFactory): ViewModelProvider.Factory
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package cash.z.ecc.android.di.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import cash.z.ecc.android.ui.base.BaseFragment
|
||||
|
||||
|
||||
inline fun <reified VM : ViewModel> BaseFragment<*>.viewModel() = object : Lazy<VM> {
|
||||
val cached: VM? = null
|
||||
override fun isInitialized(): Boolean = cached != null
|
||||
override val value: VM
|
||||
get() = cached
|
||||
?: ViewModelProvider(this@viewModel, scopedFactory<VM>())[VM::class.java]
|
||||
}
|
||||
|
||||
inline fun <reified VM : ViewModel> BaseFragment<*>.activityViewModel(isSynchronizerScope: Boolean = true) = object : Lazy<VM> {
|
||||
val cached: VM? = null
|
||||
override fun isInitialized(): Boolean = cached != null
|
||||
override val value: VM
|
||||
get() {
|
||||
return cached
|
||||
?: scopedFactory<VM>(isSynchronizerScope)?.let { factory ->
|
||||
ViewModelProvider(this@activityViewModel.mainActivity!!, factory)[VM::class.java]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified VM : ViewModel> BaseFragment<*>.scopedFactory(isSynchronizerScope: Boolean = true): ViewModelProvider.Factory {
|
||||
val factory = if (isSynchronizerScope) mainActivity?.synchronizerComponent?.viewModelFactory() else mainActivity?.component?.viewModelFactory()
|
||||
return factory ?: throw IllegalStateException("Error: mainActivity should not be null by the time the ${VM::class.java.simpleName} viewmodel is lazily accessed!")
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package cash.z.ecc.android.di.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
|
||||
class ViewModelFactory @Inject constructor(
|
||||
private val creators: Map<Class<out ViewModel>, @JvmSuppressWildcards Provider<ViewModel>>
|
||||
) : ViewModelProvider.Factory {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
val creator = creators[modelClass] ?: creators.entries.firstOrNull {
|
||||
modelClass.isAssignableFrom(it.key)
|
||||
}?.value ?: throw IllegalArgumentException(
|
||||
"No map entry found for ${modelClass.canonicalName}. Verify that this ViewModel has" +
|
||||
" been added to the ViewModelModule. ${creators.keys}"
|
||||
)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return creator.get() as T
|
||||
}
|
||||
}
|
|
@ -13,35 +13,27 @@ import android.view.ViewGroup
|
|||
import android.view.WindowManager
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.findNavController
|
||||
import cash.z.ecc.android.R
|
||||
import cash.z.ecc.android.ZcashWalletApp
|
||||
import cash.z.ecc.android.di.annotation.ActivityScope
|
||||
import cash.z.ecc.android.feedback.*
|
||||
import cash.z.ecc.android.di.component.MainActivitySubcomponent
|
||||
import cash.z.ecc.android.di.component.SynchronizerSubcomponent
|
||||
import cash.z.ecc.android.feedback.Feedback
|
||||
import cash.z.ecc.android.feedback.FeedbackCoordinator
|
||||
import cash.z.ecc.android.feedback.LaunchMetric
|
||||
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.ui.send.SendViewModel
|
||||
import cash.z.wallet.sdk.Initializer
|
||||
import cash.z.wallet.sdk.Synchronizer
|
||||
import cash.z.wallet.sdk.ext.twig
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.android.ContributesAndroidInjector
|
||||
import dagger.android.support.DaggerAppCompatActivity
|
||||
import dagger.multibindings.IntoSet
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
class MainActivity : DaggerAppCompatActivity() {
|
||||
|
||||
private var syncInit: (() -> Unit)? = null
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
@Inject
|
||||
lateinit var feedback: Feedback
|
||||
|
@ -50,21 +42,23 @@ class MainActivity : DaggerAppCompatActivity() {
|
|||
lateinit var feedbackCoordinator: FeedbackCoordinator
|
||||
|
||||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelProvider.Factory
|
||||
lateinit var clipboard: ClipboardManager
|
||||
|
||||
val sendViewModel: SendViewModel by viewModels { viewModelFactory }
|
||||
|
||||
lateinit var navController: NavController
|
||||
|
||||
private val mediaPlayer: MediaPlayer = MediaPlayer()
|
||||
|
||||
private var snackbar: Snackbar? = null
|
||||
|
||||
lateinit var synchronizer: Synchronizer
|
||||
lateinit var navController: NavController
|
||||
|
||||
lateinit var component: MainActivitySubcomponent
|
||||
lateinit var synchronizerComponent: SynchronizerSubcomponent
|
||||
|
||||
val clipboard get() = (getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
component = ZcashWalletApp.component.mainActivitySubcomponent().create(this).also {
|
||||
it.inject(this)
|
||||
}
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.main_activity)
|
||||
initNavigation()
|
||||
|
@ -125,27 +119,10 @@ class MainActivity : DaggerAppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
fun initSync() {
|
||||
twig("Initializing synchronizer")
|
||||
if (!::synchronizer.isInitialized) {
|
||||
twig("Synchronizer didn't exist yet (this means we're opening an existing wallet). Creating it now.")
|
||||
val initializer = Initializer(ZcashWalletApp.instance, "lightd-main.zecwallet.co", 443).also { it.open() }
|
||||
synchronizer = Synchronizer(ZcashWalletApp.instance, initializer)
|
||||
}
|
||||
fun startSync(initializer: Initializer) {
|
||||
synchronizerComponent = ZcashWalletApp.component.synchronizerSubcomponent().create(initializer)
|
||||
feedback.report(SYNC_START)
|
||||
synchronizer.start(lifecycleScope)
|
||||
if (syncInit != null) {
|
||||
syncInit!!()
|
||||
syncInit = null
|
||||
}
|
||||
}
|
||||
|
||||
fun initializeAccount(seed: ByteArray, birthday: Initializer.WalletBirthday? = null) {
|
||||
twig("Initializing accounts")
|
||||
feedback.measure(Report.MetricType.ACCOUNT_CREATED) {
|
||||
synchronizer =
|
||||
Synchronizer(ZcashWalletApp.instance, "lightd-main.zecwallet.co", 443, seed, birthday)
|
||||
}
|
||||
synchronizerComponent.synchronizer().start(lifecycleScope)
|
||||
}
|
||||
|
||||
fun playSound(fileName: String) {
|
||||
|
@ -177,7 +154,7 @@ class MainActivity : DaggerAppCompatActivity() {
|
|||
clipboard.setPrimaryClip(
|
||||
ClipData.newPlainText(
|
||||
"Z-Address",
|
||||
synchronizer.getAddress()
|
||||
synchronizerComponent.synchronizer().getAddress()
|
||||
)
|
||||
)
|
||||
showMessage("Address copied!", "Sweet")
|
||||
|
@ -213,56 +190,4 @@ class MainActivity : DaggerAppCompatActivity() {
|
|||
if (!it.isShownOrQueued) it.show()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: refactor initialization and remove the need for this
|
||||
fun onSyncInit(initBlock: () -> Unit) {
|
||||
if (::synchronizer.isInitialized) {
|
||||
initBlock()
|
||||
} else {
|
||||
syncInit = initBlock
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Module
|
||||
abstract class MainActivityModule {
|
||||
@ActivityScope
|
||||
@ContributesAndroidInjector(modules = [MainActivityProviderModule::class])
|
||||
abstract fun contributeActivity(): MainActivity
|
||||
|
||||
}
|
||||
|
||||
@Module
|
||||
class MainActivityProviderModule {
|
||||
|
||||
@Provides
|
||||
@ActivityScope
|
||||
fun provideFeedback(): Feedback = Feedback()
|
||||
|
||||
@Provides
|
||||
@ActivityScope
|
||||
fun provideFeedbackCoordinator(
|
||||
feedback: Feedback,
|
||||
defaultObservers: Set<@JvmSuppressWildcards FeedbackCoordinator.FeedbackObserver>
|
||||
): FeedbackCoordinator = FeedbackCoordinator(feedback, defaultObservers)
|
||||
|
||||
|
||||
//
|
||||
// Default Feedback Observer Set
|
||||
//
|
||||
|
||||
@Provides
|
||||
@ActivityScope
|
||||
@IntoSet
|
||||
fun provideFeedbackFile(): FeedbackCoordinator.FeedbackObserver = FeedbackFile()
|
||||
|
||||
@Provides
|
||||
@ActivityScope
|
||||
@IntoSet
|
||||
fun provideFeedbackConsole(): FeedbackCoordinator.FeedbackObserver = FeedbackConsole()
|
||||
|
||||
@Provides
|
||||
@ActivityScope
|
||||
@IntoSet
|
||||
fun provideFeedbackMixpanel(): FeedbackCoordinator.FeedbackObserver = FeedbackMixpanel()
|
||||
}
|
|
@ -5,17 +5,16 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.NonNull
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import cash.z.ecc.android.ui.MainActivity
|
||||
import dagger.android.support.DaggerFragment
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
abstract class BaseFragment<T : ViewBinding> : DaggerFragment() {
|
||||
abstract class BaseFragment<T : ViewBinding> : Fragment() {
|
||||
val mainActivity: MainActivity? get() = activity as MainActivity?
|
||||
|
||||
lateinit var binding: T
|
||||
|
|
|
@ -8,22 +8,21 @@ import androidx.paging.PagedList
|
|||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import cash.z.ecc.android.R
|
||||
import cash.z.ecc.android.databinding.FragmentDetailBinding
|
||||
import cash.z.ecc.android.di.annotation.FragmentScope
|
||||
import cash.z.ecc.android.di.viewmodel.viewModel
|
||||
import cash.z.ecc.android.ext.onClick
|
||||
import cash.z.ecc.android.ext.onClickNavUp
|
||||
import cash.z.ecc.android.feedback.FeedbackFile
|
||||
import cash.z.ecc.android.ui.base.BaseFragment
|
||||
import cash.z.wallet.sdk.entity.ConfirmedTransaction
|
||||
import cash.z.wallet.sdk.ext.collectWith
|
||||
import cash.z.wallet.sdk.ext.twig
|
||||
import dagger.Module
|
||||
import dagger.android.ContributesAndroidInjector
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import okio.Okio
|
||||
|
||||
|
||||
class WalletDetailFragment : BaseFragment<FragmentDetailBinding>() {
|
||||
|
||||
private val viewModel: WalletDetailViewModel by viewModel()
|
||||
|
||||
private lateinit var adapter: TransactionAdapter<ConfirmedTransaction>
|
||||
|
||||
override fun inflate(inflater: LayoutInflater): FragmentDetailBinding =
|
||||
|
@ -53,9 +52,7 @@ class WalletDetailFragment : BaseFragment<FragmentDetailBinding>() {
|
|||
binding.recyclerTransactions.layoutManager =
|
||||
LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false)
|
||||
adapter = TransactionAdapter()
|
||||
resumedScope.launch {
|
||||
mainActivity?.synchronizer?.clearedTransactions?.collect { onTransactionsUpdated(it) }
|
||||
}
|
||||
viewModel.transactions.collectWith(resumedScope) { onTransactionsUpdated(it) }
|
||||
binding.recyclerTransactions.adapter = adapter
|
||||
}
|
||||
|
||||
|
@ -96,12 +93,4 @@ class WalletDetailFragment : BaseFragment<FragmentDetailBinding>() {
|
|||
return it.readUtf8()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Module
|
||||
abstract class WalletDetailFragmentModule {
|
||||
@FragmentScope
|
||||
@ContributesAndroidInjector
|
||||
abstract fun contributeFragment(): WalletDetailFragment
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package cash.z.ecc.android.ui.detail
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import cash.z.wallet.sdk.Synchronizer
|
||||
import cash.z.wallet.sdk.ext.twig
|
||||
import javax.inject.Inject
|
||||
|
||||
class WalletDetailViewModel @Inject constructor() : ViewModel() {
|
||||
|
||||
@Inject
|
||||
lateinit var synchronizer: Synchronizer
|
||||
|
||||
val transactions get() = synchronizer.clearedTransactions
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
twig("WalletDetailViewModel cleared!")
|
||||
}
|
||||
}
|
|
@ -6,44 +6,38 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import cash.z.ecc.android.R
|
||||
import cash.z.ecc.android.databinding.FragmentHomeBinding
|
||||
import cash.z.ecc.android.di.annotation.FragmentScope
|
||||
import cash.z.ecc.android.di.viewmodel.activityViewModel
|
||||
import cash.z.ecc.android.di.viewmodel.viewModel
|
||||
import cash.z.ecc.android.ext.disabledIf
|
||||
import cash.z.ecc.android.ext.goneIf
|
||||
import cash.z.ecc.android.ext.onClickNavTo
|
||||
import cash.z.ecc.android.ui.base.BaseFragment
|
||||
import cash.z.ecc.android.ui.home.HomeFragment.BannerAction.*
|
||||
import cash.z.ecc.android.ui.send.SendViewModel
|
||||
import cash.z.ecc.android.ui.setup.WalletSetupViewModel
|
||||
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.NO_SEED
|
||||
import cash.z.wallet.sdk.SdkSynchronizer
|
||||
import cash.z.wallet.sdk.Synchronizer.Status.SYNCING
|
||||
import cash.z.wallet.sdk.ext.convertZatoshiToZecString
|
||||
import cash.z.wallet.sdk.ext.convertZecToZatoshi
|
||||
import cash.z.wallet.sdk.ext.safelyConvertToBigDecimal
|
||||
import cash.z.wallet.sdk.ext.twig
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dagger.Module
|
||||
import dagger.android.ContributesAndroidInjector
|
||||
import kotlinx.coroutines.channels.ConflatedBroadcastChannel
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
||||
|
||||
private lateinit var numberPad: List<TextView>
|
||||
private lateinit var uiModel: HomeViewModel.UiModel
|
||||
|
||||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelProvider.Factory
|
||||
|
||||
private val walletSetup: WalletSetupViewModel by activityViewModels { viewModelFactory }
|
||||
private val viewModel: HomeViewModel by activityViewModels { viewModelFactory }
|
||||
private val walletSetup: WalletSetupViewModel by activityViewModel(false)
|
||||
private val sendViewModel: SendViewModel by activityViewModel()
|
||||
private val viewModel: HomeViewModel by viewModel()
|
||||
|
||||
private val _typedChars = ConflatedBroadcastChannel<Char>()
|
||||
private val typedChars = _typedChars.asFlow()
|
||||
|
@ -60,20 +54,17 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||
twig("HomeFragment.onAttach")
|
||||
super.onAttach(context)
|
||||
|
||||
// call initSync either now or later (after initializing DBs with newly created seed)
|
||||
// this will call startSync either now or later (after initializing with newly created seed)
|
||||
walletSetup.checkSeed().onEach {
|
||||
twig("Checking seed")
|
||||
when(it) {
|
||||
NO_SEED -> {
|
||||
twig("Seed not found, therefore, launching seed creation flow")
|
||||
// interact with user to create, backup and verify seed
|
||||
mainActivity?.navController?.navigate(R.id.action_nav_home_to_create_wallet)
|
||||
// leads to a call to initSync(), later (after accounts are created from seed)
|
||||
}
|
||||
else -> {
|
||||
twig("Found seed. Re-opening existing wallet")
|
||||
mainActivity?.initSync()
|
||||
}
|
||||
if (it == NO_SEED) {
|
||||
// interact with user to create, backup and verify seed
|
||||
// leads to a call to startSync(), later (after accounts are created from seed)
|
||||
twig("Seed not found, therefore, launching seed creation flow")
|
||||
mainActivity?.navController?.navigate(R.id.action_nav_home_to_create_wallet)
|
||||
} else {
|
||||
twig("Found seed. Re-opening existing wallet")
|
||||
mainActivity?.startSync(walletSetup.openWallet())
|
||||
}
|
||||
}.launchIn(lifecycleScope)
|
||||
}
|
||||
|
@ -108,19 +99,15 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||
onSend()
|
||||
}
|
||||
}
|
||||
if (::uiModel.isInitialized) {
|
||||
twig("uiModel exists!")
|
||||
onModelUpdated(HomeViewModel.UiModel(), uiModel)
|
||||
} else {
|
||||
twig("uiModel does not exist!")
|
||||
mainActivity?.onSyncInit {
|
||||
viewModel.initialize(mainActivity!!.synchronizer, typedChars)
|
||||
}
|
||||
}
|
||||
// if (::uiModel.isInitialized) {
|
||||
// twig("uiModel exists!")
|
||||
// onModelUpdated(HomeViewModel.UiModel(), uiModel)
|
||||
// }
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
viewModel.initialize(typedChars)
|
||||
twig("HomeFragment.onResume resumeScope.isActive: ${resumedScope.isActive} $resumedScope")
|
||||
viewModel.uiModels.scanReduce { old, new ->
|
||||
onModelUpdated(old, new)
|
||||
|
@ -134,7 +121,7 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||
// but for some reason, this doesn't always happen, which kind of defeats the purpose
|
||||
// of having a cold stream in the view model
|
||||
resumedScope.launch {
|
||||
(mainActivity!!.synchronizer as SdkSynchronizer).refreshBalance()
|
||||
viewModel.refreshBalance()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -178,7 +165,7 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||
|
||||
fun setSendAmount(amount: String) {
|
||||
binding.textSendAmount.text = "\$$amount"
|
||||
mainActivity?.sendViewModel?.zatoshiAmount = amount.safelyConvertToBigDecimal().convertZecToZatoshi()
|
||||
sendViewModel.zatoshiAmount = amount.safelyConvertToBigDecimal().convertZecToZatoshi()
|
||||
binding.buttonSend.disabledIf(amount == "0")
|
||||
}
|
||||
|
||||
|
@ -343,12 +330,4 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
|
|||
super.onDetach()
|
||||
twig("HomeFragment.onDetach")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Module
|
||||
abstract class HomeFragmentModule {
|
||||
@FragmentScope
|
||||
@ContributesAndroidInjector
|
||||
abstract fun contributeFragment(): HomeFragment
|
||||
}
|
|
@ -2,26 +2,37 @@ package cash.z.ecc.android.ui.home
|
|||
|
||||
import android.os.Parcelable
|
||||
import androidx.lifecycle.ViewModel
|
||||
import cash.z.wallet.sdk.SdkSynchronizer
|
||||
import cash.z.wallet.sdk.Synchronizer
|
||||
import cash.z.wallet.sdk.Synchronizer.Status.DISCONNECTED
|
||||
import cash.z.wallet.sdk.Synchronizer.Status.SYNCED
|
||||
import cash.z.wallet.sdk.ext.ZcashSdk
|
||||
import cash.z.wallet.sdk.ext.ZcashSdk.MINERS_FEE_ZATOSHI
|
||||
import cash.z.wallet.sdk.ext.ZcashSdk.ZATOSHI_PER_ZEC
|
||||
import cash.z.wallet.sdk.ext.twig
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.scan
|
||||
import javax.inject.Inject
|
||||
|
||||
class HomeViewModel @Inject constructor() : ViewModel() {
|
||||
|
||||
@Inject
|
||||
lateinit var synchronizer: Synchronizer
|
||||
|
||||
lateinit var uiModels: Flow<UiModel>
|
||||
|
||||
var initialized = false
|
||||
|
||||
fun initialize(
|
||||
synchronizer: Synchronizer,
|
||||
typedChars: Flow<Char>
|
||||
) {
|
||||
twig("init called")
|
||||
if (initialized) {
|
||||
twig("Warning already initialized HomeViewModel. Ignoring call to initialize.")
|
||||
return
|
||||
}
|
||||
val zec = typedChars.scan("0") { acc, c ->
|
||||
when {
|
||||
// no-op cases
|
||||
|
@ -56,6 +67,10 @@ class HomeViewModel @Inject constructor() : ViewModel() {
|
|||
twig("HomeViewModel cleared!")
|
||||
}
|
||||
|
||||
suspend fun refreshBalance() {
|
||||
(synchronizer as SdkSynchronizer).refreshBalance()
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
data class UiModel( // <- THIS ERROR IS AN IDE BUG WITH PARCELIZE
|
||||
val status: Synchronizer.Status = DISCONNECTED,
|
||||
|
|
|
@ -8,31 +8,28 @@ import android.util.Log
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import cash.z.android.qrecycler.QRecycler
|
||||
import cash.z.ecc.android.databinding.FragmentReceiveBinding
|
||||
import cash.z.ecc.android.di.annotation.FragmentScope
|
||||
import cash.z.ecc.android.ext.onClickNavTo
|
||||
import cash.z.ecc.android.di.viewmodel.viewModel
|
||||
import cash.z.ecc.android.ext.onClickNavUp
|
||||
import cash.z.ecc.android.ui.base.BaseFragment
|
||||
import cash.z.ecc.android.ui.util.AddressPartNumberSpan
|
||||
import cash.z.wallet.sdk.ext.twig
|
||||
import dagger.Module
|
||||
import dagger.android.ContributesAndroidInjector
|
||||
import kotlinx.android.synthetic.main.fragment_receive.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.round
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class ReceiveFragment : BaseFragment<FragmentReceiveBinding>() {
|
||||
override fun inflate(inflater: LayoutInflater): FragmentReceiveBinding =
|
||||
FragmentReceiveBinding.inflate(inflater)
|
||||
|
||||
private val viewModel: ReceiveViewModel by viewModel()
|
||||
|
||||
lateinit var qrecycler: QRecycler
|
||||
|
||||
lateinit var addressParts: Array<TextView>
|
||||
|
||||
override fun inflate(inflater: LayoutInflater): FragmentReceiveBinding =
|
||||
FragmentReceiveBinding.inflate(inflater)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
addressParts = arrayOf(
|
||||
|
@ -56,9 +53,7 @@ class ReceiveFragment : BaseFragment<FragmentReceiveBinding>() {
|
|||
override fun onResume() {
|
||||
super.onResume()
|
||||
resumedScope.launch {
|
||||
mainActivity?.synchronizer?.getAddress()?.let { address ->
|
||||
onAddressLoaded(address)
|
||||
}
|
||||
onAddressLoaded(viewModel.getAddress())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,12 +92,4 @@ class ReceiveFragment : BaseFragment<FragmentReceiveBinding>() {
|
|||
|
||||
addressParts[index].text = textSpan
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Module
|
||||
abstract class ReceiveFragmentModule {
|
||||
@FragmentScope
|
||||
@ContributesAndroidInjector
|
||||
abstract fun contributeFragment(): ReceiveFragment
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package cash.z.ecc.android.ui.receive
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import cash.z.wallet.sdk.Synchronizer
|
||||
import cash.z.wallet.sdk.ext.twig
|
||||
import javax.inject.Inject
|
||||
|
||||
class ReceiveViewModel @Inject constructor() : ViewModel() {
|
||||
|
||||
@Inject
|
||||
lateinit var synchronizer: Synchronizer
|
||||
|
||||
suspend fun getAddress(): String = synchronizer.getAddress()
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
twig("WalletDetailViewModel cleared!")
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ import android.view.inputmethod.EditorInfo
|
|||
import cash.z.ecc.android.R
|
||||
import cash.z.ecc.android.databinding.FragmentSendAddressBinding
|
||||
import cash.z.ecc.android.di.annotation.FragmentScope
|
||||
import cash.z.ecc.android.di.viewmodel.viewModel
|
||||
import cash.z.ecc.android.ext.goneIf
|
||||
import cash.z.ecc.android.ext.onClickNavBack
|
||||
import cash.z.ecc.android.ui.base.BaseFragment
|
||||
|
@ -16,10 +17,13 @@ import cash.z.wallet.sdk.ext.convertZatoshiToZecString
|
|||
import cash.z.wallet.sdk.ext.twig
|
||||
import dagger.Module
|
||||
import dagger.android.ContributesAndroidInjector
|
||||
import javax.inject.Inject
|
||||
|
||||
class SendAddressFragment : BaseFragment<FragmentSendAddressBinding>(),
|
||||
ClipboardManager.OnPrimaryClipChangedListener {
|
||||
|
||||
val sendViewModel: SendViewModel by viewModel()
|
||||
|
||||
override fun inflate(inflater: LayoutInflater): FragmentSendAddressBinding =
|
||||
FragmentSendAddressBinding.inflate(inflater)
|
||||
|
||||
|
@ -35,7 +39,7 @@ class SendAddressFragment : BaseFragment<FragmentSendAddressBinding>(),
|
|||
binding.textBannerMessage.setOnClickListener {
|
||||
onPaste()
|
||||
}
|
||||
binding.textAmount.text = "Sending ${mainActivity?.sendViewModel?.zatoshiAmount.convertZatoshiToZecString(8)} ZEC"
|
||||
binding.textAmount.text = "Sending ${sendViewModel.zatoshiAmount.convertZatoshiToZecString(8)} ZEC"
|
||||
binding.inputZcashAddress.setOnEditorActionListener { v, actionId, event ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
onAddAddress()
|
||||
|
@ -47,7 +51,7 @@ class SendAddressFragment : BaseFragment<FragmentSendAddressBinding>(),
|
|||
}
|
||||
|
||||
private fun onAddAddress() {
|
||||
mainActivity?.sendViewModel?.toAddress = binding.inputZcashAddress.text.toString()
|
||||
sendViewModel.toAddress = binding.inputZcashAddress.text.toString()
|
||||
mainActivity?.navController?.navigate(R.id.action_nav_send_address_to_send_memo)
|
||||
}
|
||||
|
||||
|
@ -102,12 +106,4 @@ class SendAddressFragment : BaseFragment<FragmentSendAddressBinding>(),
|
|||
|
||||
private fun ClipboardManager.text(): CharSequence =
|
||||
primaryClip!!.getItemAt(0).coerceToText(mainActivity)
|
||||
}
|
||||
|
||||
|
||||
@Module
|
||||
abstract class SendAddressFragmentModule {
|
||||
@FragmentScope
|
||||
@ContributesAndroidInjector
|
||||
abstract fun contributeFragment(): SendAddressFragment
|
||||
}
|
||||
}
|
|
@ -6,48 +6,38 @@ import android.view.View
|
|||
import androidx.lifecycle.lifecycleScope
|
||||
import cash.z.ecc.android.R
|
||||
import cash.z.ecc.android.databinding.FragmentSendConfirmBinding
|
||||
import cash.z.ecc.android.di.annotation.FragmentScope
|
||||
import cash.z.ecc.android.di.viewmodel.viewModel
|
||||
import cash.z.ecc.android.ext.goneIf
|
||||
import cash.z.ecc.android.ext.onClickNavBack
|
||||
import cash.z.ecc.android.ui.MainActivity
|
||||
import cash.z.ecc.android.ui.base.BaseFragment
|
||||
import cash.z.wallet.sdk.ext.abbreviatedAddress
|
||||
import cash.z.wallet.sdk.ext.convertZatoshiToZecString
|
||||
import dagger.Module
|
||||
import dagger.android.ContributesAndroidInjector
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class SendConfirmFragment : BaseFragment<FragmentSendConfirmBinding>() {
|
||||
|
||||
val sendViewModel: SendViewModel by viewModel()
|
||||
|
||||
override fun inflate(inflater: LayoutInflater): FragmentSendConfirmBinding =
|
||||
FragmentSendConfirmBinding.inflate(inflater)
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
mainActivity?.apply {
|
||||
binding.buttonNext.setOnClickListener {
|
||||
onSend()
|
||||
}
|
||||
binding.backButtonHitArea.onClickNavBack()
|
||||
mainActivity?.lifecycleScope?.launch {
|
||||
binding.textConfirmation.text =
|
||||
"Send ${sendViewModel?.zatoshiAmount.convertZatoshiToZecString(8)} ZEC to ${sendViewModel?.toAddress.abbreviatedAddress()}?"
|
||||
}
|
||||
sendViewModel.memo.trim().isNotEmpty().let { hasMemo ->
|
||||
binding.radioIncludeAddress.isChecked = hasMemo
|
||||
binding.radioIncludeAddress.goneIf(!hasMemo)
|
||||
}
|
||||
binding.buttonNext.setOnClickListener {
|
||||
onSend()
|
||||
}
|
||||
binding.backButtonHitArea.onClickNavBack()
|
||||
mainActivity?.lifecycleScope?.launch {
|
||||
binding.textConfirmation.text =
|
||||
"Send ${sendViewModel.zatoshiAmount.convertZatoshiToZecString(8)} ZEC to ${sendViewModel?.toAddress.abbreviatedAddress()}?"
|
||||
}
|
||||
sendViewModel.memo.trim().isNotEmpty().let { hasMemo ->
|
||||
binding.radioIncludeAddress.isChecked = hasMemo
|
||||
binding.radioIncludeAddress.goneIf(!hasMemo)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSend() {
|
||||
mainActivity?.navController?.navigate(R.id.action_nav_send_confirm_to_send_final)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Module
|
||||
abstract class SendConfirmFragmentModule {
|
||||
@FragmentScope
|
||||
@ContributesAndroidInjector
|
||||
abstract fun contributeFragment(): SendConfirmFragment
|
||||
}
|
||||
}
|
|
@ -7,23 +7,23 @@ import android.view.View
|
|||
import androidx.lifecycle.lifecycleScope
|
||||
import cash.z.ecc.android.R
|
||||
import cash.z.ecc.android.databinding.FragmentSendFinalBinding
|
||||
import cash.z.ecc.android.di.annotation.FragmentScope
|
||||
import cash.z.ecc.android.di.viewmodel.viewModel
|
||||
import cash.z.ecc.android.ext.goneIf
|
||||
import cash.z.ecc.android.ui.base.BaseFragment
|
||||
import cash.z.wallet.sdk.entity.*
|
||||
import cash.z.wallet.sdk.ext.abbreviatedAddress
|
||||
import cash.z.wallet.sdk.ext.convertZatoshiToZecString
|
||||
import cash.z.wallet.sdk.ext.twig
|
||||
import dagger.Module
|
||||
import dagger.android.ContributesAndroidInjector
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlin.math.min
|
||||
import kotlin.random.Random
|
||||
|
||||
class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
|
||||
|
||||
val sendViewModel: SendViewModel by viewModel()
|
||||
|
||||
override fun inflate(inflater: LayoutInflater): FragmentSendFinalBinding =
|
||||
FragmentSendFinalBinding.inflate(inflater)
|
||||
|
||||
|
@ -36,8 +36,8 @@ class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
|
|||
onExit()
|
||||
}
|
||||
binding.textConfirmation.text =
|
||||
"Sending ${mainActivity?.sendViewModel?.zatoshiAmount.convertZatoshiToZecString(8)} ZEC to ${mainActivity?.sendViewModel?.toAddress?.abbreviatedAddress()}"
|
||||
mainActivity?.sendViewModel?.memo?.trim()?.isNotEmpty()?.let { hasMemo ->
|
||||
"Sending ${sendViewModel.zatoshiAmount.convertZatoshiToZecString(8)} ZEC to ${sendViewModel.toAddress.abbreviatedAddress()}"
|
||||
sendViewModel.memo?.trim()?.isNotEmpty()?.let { hasMemo ->
|
||||
binding.radioIncludeAddress.isChecked = hasMemo
|
||||
binding.radioIncludeAddress.goneIf(!hasMemo)
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
|
|||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
mainActivity?.apply {
|
||||
sendViewModel.send(synchronizer).onEach {
|
||||
sendViewModel.send().onEach {
|
||||
onPendingTxUpdated(it)
|
||||
}.launchIn(mainActivity?.lifecycleScope!!)
|
||||
}
|
||||
|
@ -92,12 +92,4 @@ class SendFinalFragment : BaseFragment<FragmentSendFinalBinding>() {
|
|||
private fun onExit() {
|
||||
mainActivity?.navController?.popBackStack(R.id.send_navigation, true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Module
|
||||
abstract class SendFinalFragmentModule {
|
||||
@FragmentScope
|
||||
@ContributesAndroidInjector
|
||||
abstract fun contributeFragment(): SendFinalFragment
|
||||
}
|
||||
}
|
|
@ -6,14 +6,14 @@ import android.view.View
|
|||
import android.view.inputmethod.EditorInfo
|
||||
import cash.z.ecc.android.R
|
||||
import cash.z.ecc.android.databinding.FragmentSendMemoBinding
|
||||
import cash.z.ecc.android.di.annotation.FragmentScope
|
||||
import cash.z.ecc.android.di.viewmodel.viewModel
|
||||
import cash.z.ecc.android.ext.onClickNavBack
|
||||
import cash.z.ecc.android.ext.onClickNavUp
|
||||
import cash.z.ecc.android.ui.base.BaseFragment
|
||||
import dagger.Module
|
||||
import dagger.android.ContributesAndroidInjector
|
||||
|
||||
class SendMemoFragment : BaseFragment<FragmentSendMemoBinding>() {
|
||||
|
||||
val sendViewModel: SendViewModel by viewModel()
|
||||
|
||||
override fun inflate(inflater: LayoutInflater): FragmentSendMemoBinding =
|
||||
FragmentSendMemoBinding.inflate(inflater)
|
||||
|
||||
|
@ -24,7 +24,7 @@ class SendMemoFragment : BaseFragment<FragmentSendMemoBinding>() {
|
|||
}
|
||||
binding.buttonSkip.setOnClickListener {
|
||||
binding.inputMemo.setText("")
|
||||
mainActivity?.sendViewModel?.memo = ""
|
||||
sendViewModel.memo = ""
|
||||
mainActivity?.navController?.navigate(R.id.action_nav_send_memo_to_send_confirm)
|
||||
}
|
||||
binding.backButtonHitArea.onClickNavBack()
|
||||
|
@ -47,15 +47,7 @@ class SendMemoFragment : BaseFragment<FragmentSendMemoBinding>() {
|
|||
}
|
||||
|
||||
private fun onAddMemo() {
|
||||
mainActivity?.sendViewModel?.memo = binding.inputMemo.text.toString()
|
||||
sendViewModel.memo = binding.inputMemo.text.toString()
|
||||
mainActivity?.navController?.navigate(R.id.action_nav_send_memo_to_send_confirm)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Module
|
||||
abstract class SendMemoFragmentModule {
|
||||
@FragmentScope
|
||||
@ContributesAndroidInjector
|
||||
abstract fun contributeFragment(): SendMemoFragment
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ package cash.z.ecc.android.ui.send
|
|||
import androidx.lifecycle.ViewModel
|
||||
import cash.z.ecc.android.lockbox.LockBox
|
||||
import cash.z.ecc.android.ui.setup.WalletSetupViewModel
|
||||
import cash.z.wallet.sdk.SdkSynchronizer
|
||||
import cash.z.wallet.sdk.Initializer
|
||||
import cash.z.wallet.sdk.Synchronizer
|
||||
import cash.z.wallet.sdk.entity.PendingTransaction
|
||||
import cash.z.wallet.sdk.ext.twig
|
||||
|
@ -11,9 +11,19 @@ import kotlinx.coroutines.flow.Flow
|
|||
import kotlinx.coroutines.flow.onEach
|
||||
import javax.inject.Inject
|
||||
|
||||
class SendViewModel @Inject constructor(var lockBox: LockBox) : ViewModel() {
|
||||
fun send(synchronizer: Synchronizer): Flow<PendingTransaction> {
|
||||
val keys = (synchronizer as SdkSynchronizer).rustBackend!!.deriveSpendingKeys(
|
||||
class SendViewModel @Inject constructor() : ViewModel() {
|
||||
|
||||
@Inject
|
||||
lateinit var lockBox: LockBox
|
||||
|
||||
@Inject
|
||||
lateinit var synchronizer: Synchronizer
|
||||
|
||||
@Inject
|
||||
lateinit var initializer: Initializer
|
||||
|
||||
fun send(): Flow<PendingTransaction> {
|
||||
val keys = initializer.deriveSpendingKeys(
|
||||
lockBox.getBytes(WalletSetupViewModel.LockBoxKey.SEED)!!
|
||||
)
|
||||
return synchronizer.sendToAddress(
|
||||
|
|
|
@ -8,13 +8,13 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.activity.addCallback
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import cash.z.ecc.android.R
|
||||
import cash.z.ecc.android.ZcashWalletApp
|
||||
import cash.z.ecc.android.databinding.FragmentBackupBinding
|
||||
import cash.z.ecc.android.di.annotation.FragmentScope
|
||||
import cash.z.ecc.android.di.viewmodel.activityViewModel
|
||||
import cash.z.ecc.android.di.viewmodel.viewModel
|
||||
import cash.z.ecc.android.feedback.Report.MetricType.SEED_PHRASE_LOADED
|
||||
import cash.z.ecc.android.feedback.measure
|
||||
import cash.z.ecc.android.lockbox.LockBox
|
||||
|
@ -23,20 +23,14 @@ import cash.z.ecc.android.ui.setup.WalletSetupViewModel.LockBoxKey
|
|||
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.SEED_WITH_BACKUP
|
||||
import cash.z.ecc.android.ui.util.AddressPartNumberSpan
|
||||
import cash.z.ecc.kotlin.mnemonic.Mnemonics
|
||||
import dagger.Module
|
||||
import dagger.android.ContributesAndroidInjector
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
class BackupFragment : BaseFragment<FragmentBackupBinding>() {
|
||||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelProvider.Factory
|
||||
|
||||
private val walletSetup: WalletSetupViewModel by activityViewModels { viewModelFactory }
|
||||
val walletSetup: WalletSetupViewModel by activityViewModel(false)
|
||||
|
||||
private var hasBackUp: Boolean? = null
|
||||
|
||||
|
@ -64,6 +58,13 @@ class BackupFragment : BaseFragment<FragmentBackupBinding>() {
|
|||
binding.buttonPositive.text = "Done"
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
mainActivity?.onBackPressedDispatcher?.addCallback(this) {
|
||||
onEnterWallet(false)
|
||||
}
|
||||
}
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
walletSetup.checkSeed().onEach {
|
||||
|
@ -75,8 +76,8 @@ class BackupFragment : BaseFragment<FragmentBackupBinding>() {
|
|||
}.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
private fun onEnterWallet() {
|
||||
if (hasBackUp != true) {
|
||||
private fun onEnterWallet(showMessage: Boolean = this.hasBackUp != true) {
|
||||
if (showMessage) {
|
||||
Toast.makeText(activity, "Backup verification coming soon!", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
mainActivity?.navController?.popBackStack(R.id.wallet_setup_navigation, true)
|
||||
|
@ -104,12 +105,4 @@ class BackupFragment : BaseFragment<FragmentBackupBinding>() {
|
|||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Module
|
||||
abstract class BackupFragmentModule {
|
||||
@FragmentScope
|
||||
@ContributesAndroidInjector
|
||||
abstract fun contributeFragment(): BackupFragment
|
||||
}
|
|
@ -6,31 +6,25 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import cash.z.ecc.android.R
|
||||
import cash.z.ecc.android.ZcashWalletApp
|
||||
import cash.z.ecc.android.databinding.FragmentLandingBinding
|
||||
import cash.z.ecc.android.di.annotation.FragmentScope
|
||||
import cash.z.ecc.android.isEmulator
|
||||
import cash.z.ecc.android.di.viewmodel.activityViewModel
|
||||
import cash.z.ecc.android.di.viewmodel.viewModel
|
||||
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_WITH_BACKUP
|
||||
import cash.z.wallet.sdk.Initializer
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dagger.Module
|
||||
import dagger.android.ContributesAndroidInjector
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
class LandingFragment : BaseFragment<FragmentLandingBinding>() {
|
||||
|
||||
@Inject
|
||||
lateinit var viewModelFactory: ViewModelProvider.Factory
|
||||
private val walletSetup: WalletSetupViewModel by activityViewModel(false)
|
||||
|
||||
private val walletSetup: WalletSetupViewModel by activityViewModels { viewModelFactory }
|
||||
private var skipCount: Int = 0
|
||||
|
||||
override fun inflate(inflater: LayoutInflater): FragmentLandingBinding =
|
||||
|
@ -103,19 +97,14 @@ class LandingFragment : BaseFragment<FragmentLandingBinding>() {
|
|||
Toast.makeText(activity, "Coming soon!", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
// AKA import wallet
|
||||
private fun onUseDevWallet() {
|
||||
val seedPhrase =
|
||||
"still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread"
|
||||
val seedPhrase = "still champion voice habit trend flight survey between bitter process artefact blind carbon truly provide dizzy crush flush breeze blouse charge solid fish spread"
|
||||
val birthday = 663174//626599
|
||||
mainActivity?.apply {
|
||||
lifecycleScope.launch {
|
||||
initializeAccount(
|
||||
walletSetup.importWallet(feedback, seedPhrase.toCharArray()),
|
||||
Initializer.loadBirthdayFromAssets(ZcashWalletApp.instance, birthday)
|
||||
)
|
||||
initSync()
|
||||
mainActivity?.startSync(walletSetup.importWallet(seedPhrase, birthday))
|
||||
}
|
||||
|
||||
binding.buttonPositive.isEnabled = true
|
||||
binding.textMessage.text = "Wallet imported! Congratulations!"
|
||||
binding.buttonNegative.text = "Skip"
|
||||
|
@ -125,17 +114,13 @@ class LandingFragment : BaseFragment<FragmentLandingBinding>() {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: move this to the ViewModel but doing so requires fixing dagger so do that as a separate PR
|
||||
private fun onNewWallet() {
|
||||
lifecycleScope.launch {
|
||||
val ogText = binding.buttonPositive.text
|
||||
binding.buttonPositive.text = "creating"
|
||||
binding.buttonPositive.isEnabled = false
|
||||
|
||||
mainActivity?.apply {
|
||||
initializeAccount(walletSetup.createWallet(feedback))
|
||||
initSync()
|
||||
}
|
||||
mainActivity?.startSync(walletSetup.newWallet())
|
||||
|
||||
binding.buttonPositive.isEnabled = true
|
||||
binding.textMessage.text = "Wallet created! Congratulations!"
|
||||
|
@ -155,11 +140,4 @@ class LandingFragment : BaseFragment<FragmentLandingBinding>() {
|
|||
skipCount = 0
|
||||
mainActivity?.navController?.popBackStack()
|
||||
}
|
||||
}
|
||||
|
||||
@Module
|
||||
abstract class LandingFragmentModule {
|
||||
@FragmentScope
|
||||
@ContributesAndroidInjector
|
||||
abstract fun contributeFragment(): LandingFragment
|
||||
}
|
|
@ -1,20 +1,32 @@
|
|||
package cash.z.ecc.android.ui.setup
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import cash.z.ecc.android.ZcashWalletApp
|
||||
import cash.z.ecc.android.feedback.Feedback
|
||||
import cash.z.ecc.android.feedback.Report
|
||||
import cash.z.ecc.android.feedback.Report.MetricType.*
|
||||
import cash.z.ecc.android.feedback.measure
|
||||
import cash.z.ecc.android.lockbox.LockBox
|
||||
import cash.z.ecc.android.ui.setup.WalletSetupViewModel.WalletSetupState.*
|
||||
import cash.z.ecc.kotlin.mnemonic.Mnemonics
|
||||
import cash.z.wallet.sdk.Initializer
|
||||
import cash.z.wallet.sdk.ext.twig
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
class WalletSetupViewModel @Inject constructor(val mnemonics: Mnemonics, val lockBox: LockBox) :
|
||||
ViewModel() {
|
||||
class WalletSetupViewModel @Inject constructor() : ViewModel() {
|
||||
|
||||
@Inject
|
||||
lateinit var mnemonics: Mnemonics
|
||||
|
||||
@Inject
|
||||
lateinit var lockBox: LockBox
|
||||
|
||||
@Inject
|
||||
lateinit var feedback: Feedback
|
||||
|
||||
enum class WalletSetupState {
|
||||
UNKNOWN, SEED_WITH_BACKUP, SEED_WITHOUT_BACKUP, NO_SEED
|
||||
|
@ -28,12 +40,42 @@ class WalletSetupViewModel @Inject constructor(val mnemonics: Mnemonics, val loc
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-open an existing wallet. This is the most common use case, where a user has previously
|
||||
* created or imported their seed and is returning to the wallet. In other words, this is the
|
||||
* non-FTUE case.
|
||||
*/
|
||||
fun openWallet(): Initializer {
|
||||
twig("Opening existing wallet")
|
||||
return ZcashWalletApp.component.initializerSubcomponent().create().run {
|
||||
initializer().open(birthdayStore().getBirthday())
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun newWallet(): Initializer {
|
||||
twig("Initializing new wallet")
|
||||
return ZcashWalletApp.component.initializerSubcomponent().create().run {
|
||||
initializer().apply {
|
||||
new(createWallet(), birthdayStore().newWalletBirthday)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun importWallet(seedPhrase: String, birthdayHeight: Int): Initializer {
|
||||
twig("Importing wallet")
|
||||
return ZcashWalletApp.component.initializerSubcomponent().create(Initializer.DefaultBirthdayStore(ZcashWalletApp.instance, birthdayHeight)).run {
|
||||
initializer().apply {
|
||||
import(importWallet(seedPhrase.toCharArray()), birthdayStore().getBirthday())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Take all the steps necessary to create a new wallet and measure how long it takes.
|
||||
*
|
||||
* @param feedback the object used for measurement.
|
||||
*/
|
||||
suspend fun createWallet(feedback: Feedback): ByteArray = withContext(Dispatchers.IO){
|
||||
private suspend fun createWallet(): ByteArray = withContext(Dispatchers.IO){
|
||||
check(!lockBox.getBoolean(LockBoxKey.HAS_SEED)) {
|
||||
"Error! Cannot create a seed when one already exists! This would overwrite the" +
|
||||
" existing seed and could lead to a loss of funds if the user has no backup!"
|
||||
|
@ -64,8 +106,7 @@ class WalletSetupViewModel @Inject constructor(val mnemonics: Mnemonics, val loc
|
|||
*
|
||||
* @param feedback the object used for measurement.
|
||||
*/
|
||||
suspend fun importWallet(
|
||||
feedback: Feedback,
|
||||
private suspend fun importWallet(
|
||||
seedPhrase: CharArray
|
||||
): ByteArray = withContext(Dispatchers.IO) {
|
||||
check(!lockBox.getBoolean(LockBoxKey.HAS_SEED)) {
|
||||
|
@ -89,8 +130,6 @@ class WalletSetupViewModel @Inject constructor(val mnemonics: Mnemonics, val loc
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
object LockBoxKey {
|
||||
const val SEED = "cash.z.ecc.android.SEED"
|
||||
const val SEED_PHRASE = "cash.z.ecc.android.SEED_PHRASE"
|
||||
|
|
|
@ -22,6 +22,7 @@ object Deps {
|
|||
}
|
||||
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 Dagger : Version("2.25.2") {
|
||||
|
|
|
@ -19,3 +19,5 @@ android.useAndroidX=true
|
|||
android.enableJetifier=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
|
||||
dagger.fastInit=enabled
|
Loading…
Reference in New Issue