Fix dagger implementation by simplifying things and removing Dagger Android.

Dagger Android is overly complex and confusing and Google admitted that they need to change it and simply its use. In the meantime, it is easier to just remove it and setup dagger the old way.
This commit is contained in:
Kevin Gorham 2020-01-06 00:01:06 -05:00
parent b2908989aa
commit 1d25feadf9
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
27 changed files with 322 additions and 316 deletions

View File

@ -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"

View File

@ -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 {

View File

@ -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
}
}

View File

@ -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
}

View File

@ -0,0 +1,20 @@
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 mainActivityComponent(): MainActivitySubcomponent.Factory
@Component.Factory
interface Factory {
fun create(@BindsInstance application: ZcashWalletApp): AppComponent
}
}

View File

@ -0,0 +1,45 @@
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.di.module.ViewModelsModule
import cash.z.ecc.android.ui.MainActivity
import cash.z.ecc.android.ui.detail.WalletDetailFragment
import cash.z.ecc.android.ui.home.HomeFragment
import cash.z.ecc.android.ui.receive.ReceiveFragment
import cash.z.ecc.android.ui.send.SendAddressFragment
import cash.z.ecc.android.ui.send.SendConfirmFragment
import cash.z.ecc.android.ui.send.SendFinalFragment
import cash.z.ecc.android.ui.send.SendMemoFragment
import cash.z.ecc.android.ui.setup.BackupFragment
import cash.z.ecc.android.ui.setup.LandingFragment
import dagger.BindsInstance
import dagger.Subcomponent
@ActivityScope
@Subcomponent(modules = [MainActivityModule::class, ViewModelsModule::class])
interface MainActivitySubcomponent {
fun inject(activity: MainActivity)
// Fragments
fun inject(fragment: HomeFragment)
fun inject(fragment: ReceiveFragment)
fun inject(fragment: SendAddressFragment)
fun inject(fragment: SendMemoFragment)
fun inject(fragment: SendConfirmFragment)
fun inject(fragment: SendFinalFragment)
fun inject(fragment: WalletDetailFragment)
fun inject(fragment: LandingFragment)
fun inject(fragment: BackupFragment)
fun viewModels(): ViewModelProvider
fun viewModelFactory(): ViewModelProvider.Factory
@Subcomponent.Factory
interface Factory {
fun create(@BindsInstance activity: FragmentActivity): MainActivitySubcomponent
}
}

View File

@ -1,4 +1,4 @@
package cash.z.ecc.android.di
package cash.z.ecc.android.di.module
import dagger.Module

View File

@ -0,0 +1,22 @@
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.MainActivitySubcomponent
import dagger.Module
import dagger.Provides
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
}

View File

@ -0,0 +1,50 @@
package cash.z.ecc.android.di.module
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.ViewModelProvider
import cash.z.ecc.android.di.annotation.ActivityScope
import cash.z.ecc.android.feedback.*
import dagger.Module
import dagger.Provides
import dagger.multibindings.IntoSet
@Module
class MainActivityModule {
@Provides
@ActivityScope
fun provideViewModelProvider(activity: FragmentActivity, factory: ViewModelProvider.Factory): ViewModelProvider {
return ViewModelProvider(activity, factory)
}
@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()
}

View File

@ -1,52 +1,55 @@
package cash.z.ecc.android.di
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.ViewModelKey
import cash.z.ecc.android.di.viewmodel.ViewModelFactory
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 {
abstract class ViewModelsModule {
@Binds
abstract fun bindViewModelFactory(implementation: ViewModelFactory): ViewModelProvider.Factory
//
// Activity View Models
//
@ActivityScope
@Binds
@IntoMap
@ViewModelKey(WalletSetupViewModel::class)
abstract fun bindWalletSetupViewModel(implementation: WalletSetupViewModel): ViewModel
@Binds
@IntoMap
@ViewModelKey(HomeViewModel::class)
abstract fun bindHomeViewModel(implementation: HomeViewModel): ViewModel
abstract fun bindWalletSetupViewModel(implementation: WalletSetupViewModel): ViewModel
@ActivityScope
@Binds
@IntoMap
@ViewModelKey(SendViewModel::class)
abstract fun bindSendViewModel(implementation: SendViewModel): ViewModel
}
abstract fun bindSendViewModel(implementation: SendViewModel): ViewModel
//
// Fragment View Models
//
@ActivityScope
@Binds
@IntoMap
@ViewModelKey(HomeViewModel::class)
abstract fun bindHomeViewModel(implementation: HomeViewModel): ViewModel
//
// View Model Helpers
//
@ActivityScope
@Binds
abstract fun bindViewModelFactory(implementation: ViewModelFactory): ViewModelProvider.Factory
@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
}
}

View File

@ -0,0 +1,36 @@
package cash.z.ecc.android.di.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import cash.z.ecc.android.ui.MainActivity
import cash.z.ecc.android.ui.base.BaseFragment
inline fun <reified VM : ViewModel> MainActivity.viewModel() = object : Lazy<VM> {
val cached: VM? = null
override fun isInitialized(): Boolean = cached != null
override val value: VM get() {
return cached ?: component.viewModels()[VM::class.java]
}
}
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() {
return if (cached != null) cached else {
val factory = mainActivity?.component?.viewModelFactory()
?: throw IllegalStateException("Error: mainActivity should not be null by the time the ${VM::class.java.simpleName} viewmodel is lazily accessed!")
ViewModelProvider(this@viewModel, factory)[VM::class.java]
}
}
}
inline fun <reified VM : ViewModel> BaseFragment<*>.activityViewModel() = object : Lazy<VM> {
val cached: VM? = null
override fun isInitialized(): Boolean = cached != null
override val value: VM get() {
return cached ?: mainActivity?.component?.viewModels()?.get(VM::class.java)
?: throw IllegalStateException("Error: mainActivity should not be null by the time the ${VM::class.java.simpleName} activityViewModel is lazily accessed!")
}
}

View File

@ -0,0 +1,23 @@
package cash.z.ecc.android.di.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import cash.z.ecc.android.di.annotation.ActivityScope
import javax.inject.Inject
import javax.inject.Provider
@ActivityScope
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
}
}

View File

@ -13,15 +13,15 @@ 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.di.component.MainActivitySubcomponent
import cash.z.ecc.android.di.viewmodel.viewModel
import cash.z.ecc.android.feedback.*
import cash.z.ecc.android.feedback.Report.NonUserAction.FEEDBACK_STOPPED
import cash.z.ecc.android.feedback.Report.NonUserAction.SYNC_START
@ -30,16 +30,11 @@ 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() {
class MainActivity : AppCompatActivity() {
private var syncInit: (() -> Unit)? = null
@ -50,21 +45,26 @@ class MainActivity : DaggerAppCompatActivity() {
lateinit var feedbackCoordinator: FeedbackCoordinator
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
lateinit var clipboard: ClipboardManager
val sendViewModel: SendViewModel by viewModels { viewModelFactory }
val sendViewModel: SendViewModel by viewModel()
lateinit var navController: NavController
private val mediaPlayer: MediaPlayer = MediaPlayer()
private var snackbar: Snackbar? = null
lateinit var navController: NavController
lateinit var synchronizer: Synchronizer
val clipboard get() = (getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager)
lateinit var component: MainActivitySubcomponent
override fun onCreate(savedInstanceState: Bundle?) {
component = ZcashWalletApp.component.mainActivityComponent().create(this).also {
it.inject(this)
}
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
initNavigation()
@ -223,46 +223,3 @@ class MainActivity : DaggerAppCompatActivity() {
}
}
}
@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()
}

View File

@ -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

View File

@ -8,15 +8,12 @@ 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.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.twig
import dagger.Module
import dagger.android.ContributesAndroidInjector
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import okio.Okio
@ -96,12 +93,4 @@ class WalletDetailFragment : BaseFragment<FragmentDetailBinding>() {
return it.readUtf8()
}
}
}
@Module
abstract class WalletDetailFragmentModule {
@FragmentScope
@ContributesAndroidInjector
abstract fun contributeFragment(): WalletDetailFragment
}

View File

@ -6,17 +6,17 @@ 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
@ -26,24 +26,19 @@ 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()
private val sendViewModel: SendViewModel by activityViewModel()
private val viewModel: HomeViewModel by viewModel()
private val _typedChars = ConflatedBroadcastChannel<Char>()
private val typedChars = _typedChars.asFlow()
@ -58,6 +53,7 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
override fun onAttach(context: Context) {
twig("HomeFragment.onAttach")
mainActivity?.component?.inject(this)
super.onAttach(context)
// call initSync either now or later (after initializing DBs with newly created seed)
@ -178,7 +174,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 +339,4 @@ class HomeFragment : BaseFragment<FragmentHomeBinding>() {
super.onDetach()
twig("HomeFragment.onDetach")
}
}
@Module
abstract class HomeFragmentModule {
@FragmentScope
@ContributesAndroidInjector
abstract fun contributeFragment(): HomeFragment
}

View File

@ -8,21 +8,14 @@ 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.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>() {
@ -97,12 +90,4 @@ class ReceiveFragment : BaseFragment<FragmentReceiveBinding>() {
addressParts[index].text = textSpan
}
}
@Module
abstract class ReceiveFragmentModule {
@FragmentScope
@ContributesAndroidInjector
abstract fun contributeFragment(): ReceiveFragment
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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)
}
@ -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
}
}

View File

@ -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
}
}

View File

@ -11,7 +11,11 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onEach
import javax.inject.Inject
class SendViewModel @Inject constructor(var lockBox: LockBox) : ViewModel() {
class SendViewModel @Inject constructor() : ViewModel() {
@Inject
lateinit var lockBox: LockBox
fun send(synchronizer: Synchronizer): Flow<PendingTransaction> {
val keys = (synchronizer as SdkSynchronizer).rustBackend!!.deriveSpendingKeys(
lockBox.getBytes(WalletSetupViewModel.LockBoxKey.SEED)!!

View File

@ -8,13 +8,11 @@ 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.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.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 +21,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 viewModel()
private var hasBackUp: Boolean? = null
@ -104,12 +96,4 @@ class BackupFragment : BaseFragment<FragmentBackupBinding>() {
result
}
}
}
@Module
abstract class BackupFragmentModule {
@FragmentScope
@ContributesAndroidInjector
abstract fun contributeFragment(): BackupFragment
}

View File

@ -5,32 +5,24 @@ import android.os.Bundle
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.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
val walletSetup: WalletSetupViewModel by viewModel()
private val walletSetup: WalletSetupViewModel by activityViewModels { viewModelFactory }
private var skipCount: Int = 0
override fun inflate(inflater: LayoutInflater): FragmentLandingBinding =
@ -155,11 +147,4 @@ class LandingFragment : BaseFragment<FragmentLandingBinding>() {
skipCount = 0
mainActivity?.navController?.popBackStack()
}
}
@Module
abstract class LandingFragmentModule {
@FragmentScope
@ContributesAndroidInjector
abstract fun contributeFragment(): LandingFragment
}

View File

@ -13,8 +13,13 @@ 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
enum class WalletSetupState {
UNKNOWN, SEED_WITH_BACKUP, SEED_WITHOUT_BACKUP, NO_SEED
@ -89,8 +94,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"

View File

@ -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") {

View File

@ -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