diff --git a/app/build.gradle b/app/build.gradle index 0328934..01b0f9e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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" diff --git a/app/src/main/java/cash/z/ecc/android/ZcashWalletApp.kt b/app/src/main/java/cash/z/ecc/android/ZcashWalletApp.kt index 769d6be..84af5ba 100644 --- a/app/src/main/java/cash/z/ecc/android/ZcashWalletApp.kt +++ b/app/src/main/java/cash/z/ecc/android/ZcashWalletApp.kt @@ -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 { - 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 { diff --git a/app/src/main/java/cash/z/ecc/android/di/AppComponent.kt b/app/src/main/java/cash/z/ecc/android/di/AppComponent.kt deleted file mode 100644 index fc2f176..0000000 --- a/app/src/main/java/cash/z/ecc/android/di/AppComponent.kt +++ /dev/null @@ -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 { - - @Component.Factory - interface Factory { - fun create(@BindsInstance application: ZcashWalletApp): AppComponent - } -} \ No newline at end of file diff --git a/app/src/main/java/cash/z/ecc/android/di/AppModule.kt b/app/src/main/java/cash/z/ecc/android/di/AppModule.kt deleted file mode 100644 index d1cecfb..0000000 --- a/app/src/main/java/cash/z/ecc/android/di/AppModule.kt +++ /dev/null @@ -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 -} diff --git a/app/src/main/java/cash/z/ecc/android/di/component/AppComponent.kt b/app/src/main/java/cash/z/ecc/android/di/component/AppComponent.kt new file mode 100644 index 0000000..12e02bf --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/di/component/AppComponent.kt @@ -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 + } +} \ No newline at end of file diff --git a/app/src/main/java/cash/z/ecc/android/di/component/MainActivitySubcomponent.kt b/app/src/main/java/cash/z/ecc/android/di/component/MainActivitySubcomponent.kt new file mode 100644 index 0000000..e96809a --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/di/component/MainActivitySubcomponent.kt @@ -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 + } +} \ No newline at end of file diff --git a/app/src/main/java/cash/z/ecc/android/di/AppBindingModule.kt b/app/src/main/java/cash/z/ecc/android/di/module/AppBindingModule.kt similarity index 64% rename from app/src/main/java/cash/z/ecc/android/di/AppBindingModule.kt rename to app/src/main/java/cash/z/ecc/android/di/module/AppBindingModule.kt index eb5a0ad..8ebfbf0 100644 --- a/app/src/main/java/cash/z/ecc/android/di/AppBindingModule.kt +++ b/app/src/main/java/cash/z/ecc/android/di/module/AppBindingModule.kt @@ -1,4 +1,4 @@ -package cash.z.ecc.android.di +package cash.z.ecc.android.di.module import dagger.Module diff --git a/app/src/main/java/cash/z/ecc/android/di/module/AppModule.kt b/app/src/main/java/cash/z/ecc/android/di/module/AppModule.kt new file mode 100644 index 0000000..abeaadd --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/di/module/AppModule.kt @@ -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 +} diff --git a/app/src/main/java/cash/z/ecc/android/di/module/MainActivityModule.kt b/app/src/main/java/cash/z/ecc/android/di/module/MainActivityModule.kt new file mode 100644 index 0000000..db78bd4 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/di/module/MainActivityModule.kt @@ -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() +} diff --git a/app/src/main/java/cash/z/ecc/android/di/ViewModelModule.kt b/app/src/main/java/cash/z/ecc/android/di/module/ViewModelsModule.kt similarity index 50% rename from app/src/main/java/cash/z/ecc/android/di/ViewModelModule.kt rename to app/src/main/java/cash/z/ecc/android/di/module/ViewModelsModule.kt index 074f4b9..3d23355 100644 --- a/app/src/main/java/cash/z/ecc/android/di/ViewModelModule.kt +++ b/app/src/main/java/cash/z/ecc/android/di/module/ViewModelsModule.kt @@ -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, @JvmSuppressWildcards Provider> -) : ViewModelProvider.Factory { - override fun create(modelClass: Class): 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 - } } \ No newline at end of file diff --git a/app/src/main/java/cash/z/ecc/android/di/viewmodel/ViewModelExt.kt b/app/src/main/java/cash/z/ecc/android/di/viewmodel/ViewModelExt.kt new file mode 100644 index 0000000..827dec3 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/di/viewmodel/ViewModelExt.kt @@ -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 MainActivity.viewModel() = object : Lazy { + val cached: VM? = null + override fun isInitialized(): Boolean = cached != null + override val value: VM get() { + return cached ?: component.viewModels()[VM::class.java] + } +} + +inline fun BaseFragment<*>.viewModel() = object : Lazy { + 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 BaseFragment<*>.activityViewModel() = object : Lazy { + 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!") + } +} \ No newline at end of file diff --git a/app/src/main/java/cash/z/ecc/android/di/viewmodel/ViewModelFactory.kt b/app/src/main/java/cash/z/ecc/android/di/viewmodel/ViewModelFactory.kt new file mode 100644 index 0000000..2f0a0b4 --- /dev/null +++ b/app/src/main/java/cash/z/ecc/android/di/viewmodel/ViewModelFactory.kt @@ -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, @JvmSuppressWildcards Provider> +) : ViewModelProvider.Factory { + override fun create(modelClass: Class): 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/cash/z/ecc/android/ui/MainActivity.kt b/app/src/main/java/cash/z/ecc/android/ui/MainActivity.kt index ce92237..459b9ae 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/MainActivity.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/MainActivity.kt @@ -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() -} \ No newline at end of file diff --git a/app/src/main/java/cash/z/ecc/android/ui/base/BaseFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/base/BaseFragment.kt index 12e7598..4e45d90 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/base/BaseFragment.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/base/BaseFragment.kt @@ -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 : DaggerFragment() { +abstract class BaseFragment : Fragment() { val mainActivity: MainActivity? get() = activity as MainActivity? lateinit var binding: T diff --git a/app/src/main/java/cash/z/ecc/android/ui/detail/WalletDetailFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/detail/WalletDetailFragment.kt index c1bd601..83f25ae 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/detail/WalletDetailFragment.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/detail/WalletDetailFragment.kt @@ -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() { return it.readUtf8() } } -} - - -@Module -abstract class WalletDetailFragmentModule { - @FragmentScope - @ContributesAndroidInjector - abstract fun contributeFragment(): WalletDetailFragment } \ No newline at end of file diff --git a/app/src/main/java/cash/z/ecc/android/ui/home/HomeFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/home/HomeFragment.kt index 4409734..3b2b4af 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/home/HomeFragment.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/home/HomeFragment.kt @@ -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() { private lateinit var numberPad: List 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() private val typedChars = _typedChars.asFlow() @@ -58,6 +53,7 @@ class HomeFragment : BaseFragment() { 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() { 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() { super.onDetach() twig("HomeFragment.onDetach") } -} - - -@Module -abstract class HomeFragmentModule { - @FragmentScope - @ContributesAndroidInjector - abstract fun contributeFragment(): HomeFragment } \ No newline at end of file diff --git a/app/src/main/java/cash/z/ecc/android/ui/receive/ReceiveFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/receive/ReceiveFragment.kt index 5711695..53d1fac 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/receive/ReceiveFragment.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/receive/ReceiveFragment.kt @@ -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() { @@ -97,12 +90,4 @@ class ReceiveFragment : BaseFragment() { addressParts[index].text = textSpan } -} - - -@Module -abstract class ReceiveFragmentModule { - @FragmentScope - @ContributesAndroidInjector - abstract fun contributeFragment(): ReceiveFragment } \ No newline at end of file diff --git a/app/src/main/java/cash/z/ecc/android/ui/send/SendAddressFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/send/SendAddressFragment.kt index 69abc5a..a7ff187 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/send/SendAddressFragment.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/send/SendAddressFragment.kt @@ -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(), ClipboardManager.OnPrimaryClipChangedListener { + val sendViewModel: SendViewModel by viewModel() + override fun inflate(inflater: LayoutInflater): FragmentSendAddressBinding = FragmentSendAddressBinding.inflate(inflater) @@ -35,7 +39,7 @@ class SendAddressFragment : BaseFragment(), 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(), } 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(), private fun ClipboardManager.text(): CharSequence = primaryClip!!.getItemAt(0).coerceToText(mainActivity) -} - - -@Module -abstract class SendAddressFragmentModule { - @FragmentScope - @ContributesAndroidInjector - abstract fun contributeFragment(): SendAddressFragment -} +} \ No newline at end of file diff --git a/app/src/main/java/cash/z/ecc/android/ui/send/SendConfirmFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/send/SendConfirmFragment.kt index de0b212..aa88936 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/send/SendConfirmFragment.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/send/SendConfirmFragment.kt @@ -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() { + + 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 -} +} \ No newline at end of file diff --git a/app/src/main/java/cash/z/ecc/android/ui/send/SendFinalFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/send/SendFinalFragment.kt index ff8ea1e..a064a0b 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/send/SendFinalFragment.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/send/SendFinalFragment.kt @@ -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() { + + val sendViewModel: SendViewModel by viewModel() + override fun inflate(inflater: LayoutInflater): FragmentSendFinalBinding = FragmentSendFinalBinding.inflate(inflater) @@ -36,8 +36,8 @@ class SendFinalFragment : BaseFragment() { 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() { private fun onExit() { mainActivity?.navController?.popBackStack(R.id.send_navigation, true) } -} - - -@Module -abstract class SendFinalFragmentModule { - @FragmentScope - @ContributesAndroidInjector - abstract fun contributeFragment(): SendFinalFragment -} +} \ No newline at end of file diff --git a/app/src/main/java/cash/z/ecc/android/ui/send/SendMemoFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/send/SendMemoFragment.kt index 39a8a26..7abe80d 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/send/SendMemoFragment.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/send/SendMemoFragment.kt @@ -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() { + + val sendViewModel: SendViewModel by viewModel() + override fun inflate(inflater: LayoutInflater): FragmentSendMemoBinding = FragmentSendMemoBinding.inflate(inflater) @@ -24,7 +24,7 @@ class SendMemoFragment : BaseFragment() { } 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() { } 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 -} +} \ No newline at end of file diff --git a/app/src/main/java/cash/z/ecc/android/ui/send/SendViewModel.kt b/app/src/main/java/cash/z/ecc/android/ui/send/SendViewModel.kt index 5040c5d..cd6bd22 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/send/SendViewModel.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/send/SendViewModel.kt @@ -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 { val keys = (synchronizer as SdkSynchronizer).rustBackend!!.deriveSpendingKeys( lockBox.getBytes(WalletSetupViewModel.LockBoxKey.SEED)!! diff --git a/app/src/main/java/cash/z/ecc/android/ui/setup/BackupFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/setup/BackupFragment.kt index 9dc5cb6..36a9541 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/setup/BackupFragment.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/setup/BackupFragment.kt @@ -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() { - @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() { result } } -} - - -@Module -abstract class BackupFragmentModule { - @FragmentScope - @ContributesAndroidInjector - abstract fun contributeFragment(): BackupFragment } \ No newline at end of file diff --git a/app/src/main/java/cash/z/ecc/android/ui/setup/LandingFragment.kt b/app/src/main/java/cash/z/ecc/android/ui/setup/LandingFragment.kt index de010d2..683dbd6 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/setup/LandingFragment.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/setup/LandingFragment.kt @@ -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() { - @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() { skipCount = 0 mainActivity?.navController?.popBackStack() } -} - -@Module -abstract class LandingFragmentModule { - @FragmentScope - @ContributesAndroidInjector - abstract fun contributeFragment(): LandingFragment } \ No newline at end of file diff --git a/app/src/main/java/cash/z/ecc/android/ui/setup/WalletSetupViewModel.kt b/app/src/main/java/cash/z/ecc/android/ui/setup/WalletSetupViewModel.kt index 4dfe047..466a0a4 100644 --- a/app/src/main/java/cash/z/ecc/android/ui/setup/WalletSetupViewModel.kt +++ b/app/src/main/java/cash/z/ecc/android/ui/setup/WalletSetupViewModel.kt @@ -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" diff --git a/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt b/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt index 1fd4529..7c59aa8 100644 --- a/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt +++ b/buildSrc/src/main/java/cash/z/ecc/android/Dependencies.kt @@ -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") { diff --git a/gradle.properties b/gradle.properties index 23339e0..dab4578 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 \ No newline at end of file