fixes to scoping and job cancellation

Utilize fragmentscope in the AndroidInjector subcomponents for fragments and also allow presenters to restart jobs
This commit is contained in:
Kevin Gorham 2019-02-23 16:39:29 -05:00 committed by Kevin Gorham
parent ab58b03db6
commit e79c68fd20
20 changed files with 270 additions and 237 deletions

View File

@ -3,4 +3,5 @@ package cash.z.android.wallet.di.annotation
import javax.inject.Scope import javax.inject.Scope
@Scope @Scope
annotation class ActivityScoped @Retention(AnnotationRetention.SOURCE)
annotation class ActivityScope

View File

@ -0,0 +1,7 @@
package cash.z.android.wallet.di.annotation
import javax.inject.Scope
@Scope
@Retention(AnnotationRetention.SOURCE)
annotation class ApplicationScope

View File

@ -3,4 +3,5 @@ package cash.z.android.wallet.di.annotation
import javax.inject.Scope import javax.inject.Scope
@Scope @Scope
annotation class ApplicationScoped @Retention(AnnotationRetention.SOURCE)
annotation class FragmentScope

View File

@ -23,9 +23,9 @@ import javax.inject.Singleton
MainActivityModule::class, MainActivityModule::class,
// Injected Fragments // Injected Fragments
HomeFragmentModule::class,
AboutFragmentModule::class, AboutFragmentModule::class,
HistoryFragmentModule::class, HistoryFragmentModule::class,
HomeFragmentModule::class,
WelcomeFragmentModule::class, WelcomeFragmentModule::class,
ReceiveFragmentModule::class, ReceiveFragmentModule::class,
RequestFragmentModule::class, RequestFragmentModule::class,

View File

@ -56,10 +56,10 @@ class MainActivity : BaseActivity() {
// used to manage the drawer and drawerToggle interactions // used to manage the drawer and drawerToggle interactions
private lateinit var appBarConfiguration: AppBarConfiguration private lateinit var appBarConfiguration: AppBarConfiguration
lateinit var navController: NavController lateinit var navController: NavController
private val multiStartNavigationUi = MultiStartNavigationUI(listOf( // private val multiStartNavigationUi = MultiStartNavigationUI(listOf(
R.id.nav_home_fragment, // R.id.nav_home_fragment,
R.id.nav_welcome_fragment // R.id.nav_welcome_fragment
)) // ))
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -114,10 +114,10 @@ class MainActivity : BaseActivity() {
navController = Navigation.findNavController(this, R.id.nav_host_fragment).also { n -> navController = Navigation.findNavController(this, R.id.nav_host_fragment).also { n ->
appBarConfiguration = AppBarConfiguration(n.graph, binding.drawerLayout).also { a -> appBarConfiguration = AppBarConfiguration(n.graph, binding.drawerLayout).also { a ->
binding.navView.setupWithNavController(n) binding.navView.setupWithNavController(n)
multiStartNavigationUi.setupActionBarWithNavController(this, n, binding.drawerLayout) setupActionBarWithNavController(n, binding.drawerLayout)
} }
} }
navController.addOnNavigatedListener { _, _ -> navController.addOnDestinationChangedListener { _, _, _ ->
// hide the keyboard anytime we change destinations // hide the keyboard anytime we change destinations
getSystemService<InputMethodManager>()?.hideSoftInputFromWindow(binding.navView.windowToken, HIDE_NOT_ALWAYS) getSystemService<InputMethodManager>()?.hideSoftInputFromWindow(binding.navView.windowToken, HIDE_NOT_ALWAYS)
} }
@ -170,80 +170,80 @@ abstract class MainActivityModule {
abstract fun contributeMainActivity(): MainActivity abstract fun contributeMainActivity(): MainActivity
} }
//
class MultiStartNavigationUI(private val startDestinations: List<Int>) { //class MultiStartNavigationUI(private val startDestinations: List<Int>) {
fun setupActionBarWithNavController(activity: AppCompatActivity, navController: NavController, // fun setupActionBarWithNavController(activity: AppCompatActivity, navController: NavController,
drawerLayout: DrawerLayout?) { // drawerLayout: DrawerLayout?) {
//
navController.addOnNavigatedListener(ActionBarOnNavigatedListener( // navController.addOnNavigatedListener(ActionBarOnNavigatedListener(
activity, startDestinations, drawerLayout)) // activity, startDestinations, drawerLayout))
} // }
//
fun navigateUp(drawerLayout: DrawerLayout?, navController: NavController): Boolean { // fun navigateUp(drawerLayout: DrawerLayout?, navController: NavController): Boolean {
if (drawerLayout != null && startDestinations.contains(navController.currentDestination?.id)) { // if (drawerLayout != null && startDestinations.contains(navController.currentDestination?.id)) {
drawerLayout.openDrawer(GravityCompat.START) // drawerLayout.openDrawer(GravityCompat.START)
return true // return true
} else { // } else {
return navController.navigateUp() // return navController.navigateUp()
} // }
} // }
//
fun onBackPressed(activity: AppCompatActivity, // fun onBackPressed(activity: AppCompatActivity,
navController: NavController): Boolean { // navController: NavController): Boolean {
if (startDestinations.contains(navController.currentDestination?.id)) { // if (startDestinations.contains(navController.currentDestination?.id)) {
ActivityCompat.finishAfterTransition(activity) // ActivityCompat.finishAfterTransition(activity)
return true // return true
} // }
//
return false // return false
} // }
//
private class ActionBarOnNavigatedListener( // private class ActionBarOnNavigatedListener(
private val mActivity: AppCompatActivity, // private val mActivity: AppCompatActivity,
private val startDestinations: List<Int>, // private val startDestinations: List<Int>,
private val mDrawerLayout: DrawerLayout? // private val mDrawerLayout: DrawerLayout?
) : NavController.OnNavigatedListener { // ) : NavController.OnNavigatedListener {
private var mArrowDrawable: DrawerArrowDrawable? = null // private var mArrowDrawable: DrawerArrowDrawable? = null
private var mAnimator: ValueAnimator? = null // private var mAnimator: ValueAnimator? = null
//
override fun onNavigated(controller: NavController, destination: NavDestination) { // override fun onNavigated(controller: NavController, destination: NavDestination) {
val actionBar = mActivity.supportActionBar // val actionBar = mActivity.supportActionBar
//
val title = destination.label // val title = destination.label
if (!title.isNullOrEmpty()) { // if (!title.isNullOrEmpty()) {
actionBar?.title = title // actionBar?.title = title
} // }
//
val isStartDestination = startDestinations.contains(destination.id) // val isStartDestination = startDestinations.contains(destination.id)
actionBar?.setDisplayHomeAsUpEnabled(this.mDrawerLayout != null || !isStartDestination) // actionBar?.setDisplayHomeAsUpEnabled(this.mDrawerLayout != null || !isStartDestination)
setActionBarUpIndicator(mDrawerLayout != null && isStartDestination) // setActionBarUpIndicator(mDrawerLayout != null && isStartDestination)
} // }
//
//
private fun setActionBarUpIndicator(showAsDrawerIndicator: Boolean) { // private fun setActionBarUpIndicator(showAsDrawerIndicator: Boolean) {
val delegate = mActivity.drawerToggleDelegate // val delegate = mActivity.drawerToggleDelegate
var animate = true // var animate = true
if (mArrowDrawable == null) { // if (mArrowDrawable == null) {
mArrowDrawable = DrawerArrowDrawable(delegate!!.actionBarThemedContext) // mArrowDrawable = DrawerArrowDrawable(delegate!!.actionBarThemedContext)
delegate.setActionBarUpIndicator(mArrowDrawable, 0) // delegate.setActionBarUpIndicator(mArrowDrawable, 0)
animate = false // animate = false
} // }
//
mArrowDrawable?.let { // mArrowDrawable?.let {
val endValue = if (showAsDrawerIndicator) 0.0f else 1.0f // val endValue = if (showAsDrawerIndicator) 0.0f else 1.0f
//
if (animate) { // if (animate) {
val startValue = it.progress // val startValue = it.progress
mAnimator?.cancel() // mAnimator?.cancel()
//
@SuppressLint("ObjectAnimatorBinding") // @SuppressLint("ObjectAnimatorBinding")
mAnimator = ObjectAnimator.ofFloat(it, "progress", startValue, endValue) // mAnimator = ObjectAnimator.ofFloat(it, "progress", startValue, endValue)
mAnimator?.start() // mAnimator?.start()
} else { // } else {
it.progress = endValue // it.progress = endValue
} // }
} // }
//
} // }
} // }
} //}

View File

@ -6,6 +6,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.doOnPreDraw import androidx.core.view.doOnPreDraw
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.navigation.NavOptions
import androidx.transition.Transition import androidx.transition.Transition
import androidx.transition.TransitionInflater import androidx.transition.TransitionInflater
import cash.z.android.wallet.R import cash.z.android.wallet.R
@ -38,7 +39,12 @@ class FirstrunFragment : ProgressFragment(R.id.progress_firstrun), Transition.Tr
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
postponeEnterTransition() postponeEnterTransition()
binding.buttonNext.setOnClickListener { binding.buttonNext.setOnClickListener {
mainActivity?.navController?.navigate(R.id.nav_sync_fragment) mainActivity?.navController?.navigate(
R.id.action_firstrun_fragment_to_sync_fragment,
null,
NavOptions.Builder().setPopUpTo(R.id.mobile_navigation, true).build(),
null
)
} }
// binding.buttonNext.alpha = 0f // binding.buttonNext.alpha = 0f
// binding.textProgressFirstrun.alpha = 0f // binding.textProgressFirstrun.alpha = 0f

View File

@ -11,15 +11,13 @@ import cash.z.android.wallet.R
import cash.z.android.wallet.databinding.FragmentHistoryBinding import cash.z.android.wallet.databinding.FragmentHistoryBinding
import cash.z.android.wallet.ui.adapter.TransactionAdapter import cash.z.android.wallet.ui.adapter.TransactionAdapter
import cash.z.android.wallet.ui.presenter.HistoryPresenter import cash.z.android.wallet.ui.presenter.HistoryPresenter
import cash.z.android.wallet.ui.presenter.Presenter import cash.z.android.wallet.ui.presenter.HistoryPresenterModule
import cash.z.android.wallet.ui.util.AlternatingRowColorDecoration import cash.z.android.wallet.ui.util.AlternatingRowColorDecoration
import cash.z.wallet.sdk.dao.WalletTransaction import cash.z.wallet.sdk.dao.WalletTransaction
import dagger.Binds
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton
class HistoryFragment : BaseFragment(), HistoryPresenter.HistoryView { class HistoryFragment : BaseFragment(), HistoryPresenter.HistoryView {
@ -73,10 +71,6 @@ class HistoryFragment : BaseFragment(), HistoryPresenter.HistoryView {
@Module @Module
abstract class HistoryFragmentModule { abstract class HistoryFragmentModule {
@ContributesAndroidInjector @ContributesAndroidInjector(modules = [HistoryPresenterModule::class])
abstract fun contributeHistoryFragment(): HistoryFragment abstract fun contributeHistoryFragment(): HistoryFragment
@Binds
@Singleton
abstract fun providePresenter(historyPresenter: HistoryPresenter): Presenter
} }

View File

@ -22,10 +22,12 @@ import androidx.transition.Transition
import androidx.transition.TransitionInflater import androidx.transition.TransitionInflater
import cash.z.android.wallet.R import cash.z.android.wallet.R
import cash.z.android.wallet.databinding.FragmentHomeBinding import cash.z.android.wallet.databinding.FragmentHomeBinding
import cash.z.android.wallet.di.annotation.FragmentScope
import cash.z.android.wallet.extention.* import cash.z.android.wallet.extention.*
import cash.z.android.wallet.sample.SampleProperties import cash.z.android.wallet.sample.SampleProperties
import cash.z.android.wallet.ui.adapter.TransactionAdapter import cash.z.android.wallet.ui.adapter.TransactionAdapter
import cash.z.android.wallet.ui.presenter.HomePresenter import cash.z.android.wallet.ui.presenter.HomePresenter
import cash.z.android.wallet.ui.presenter.HomePresenterModule
import cash.z.android.wallet.ui.presenter.Presenter import cash.z.android.wallet.ui.presenter.Presenter
import cash.z.android.wallet.ui.util.AlternatingRowColorDecoration import cash.z.android.wallet.ui.util.AlternatingRowColorDecoration
import cash.z.android.wallet.ui.util.LottieLooper import cash.z.android.wallet.ui.util.LottieLooper
@ -43,7 +45,6 @@ import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton
import kotlin.random.Random import kotlin.random.Random
import kotlin.random.nextLong import kotlin.random.nextLong
@ -59,6 +60,7 @@ class HomeFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener, HomeP
private lateinit var binding: FragmentHomeBinding private lateinit var binding: FragmentHomeBinding
private lateinit var zcashLogoAnimation: LottieLooper private lateinit var zcashLogoAnimation: LottieLooper
private var maxTransactionsShown: Int = 12
private var snackbar: Snackbar? = null private var snackbar: Snackbar? = null
private var viewsInitialized = false private var viewsInitialized = false
@ -66,6 +68,11 @@ class HomeFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener, HomeP
private var clock: Handler = Handler() private var clock: Handler = Handler()
private val tickIfNeeded = Ticker() private val tickIfNeeded = Ticker()
//
// LifeCycle
//
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
@ -89,7 +96,6 @@ class HomeFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener, HomeP
} }
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
initTemp() initTemp()
@ -128,15 +134,64 @@ class HomeFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener, HomeP
} }
//
// SendView Implementation
//
override fun setTransactions(transactions: List<WalletTransaction>) {
val recent = if(transactions.size > maxTransactionsShown) transactions.subList(0, maxTransactionsShown) else transactions
with (binding.includeContent.recyclerTransactions) {
(adapter as TransactionAdapter).submitList(recent)
postDelayed({
smoothScrollToPosition(0)
}, 100L)
}
// Show "See All" when we have a sublist on screen
if (recent.size != transactions.size) {
binding.includeContent.textTransactionHeaderSeeAll.visibility = View.VISIBLE
}
onContentRefreshComplete(transactions.isEmpty())
}
//TODO: pull some of this logic into the presenter, particularly the part that deals with ZEC <-> USD price conversion
override fun updateBalance(old: Long, new: Long) {
val zecValue = new.convertZatoshiToZec()
setZecValue(zecValue.toZecString(3))
setUsdValue(zecValue.convertZecToUsd(SampleProperties.USD_PER_ZEC).toUsdString())
onContentRefreshComplete(new <= 0)
}
override fun setActiveTransactions(activeTransactionMap: Map<ActiveTransaction, TransactionState>) {
if (activeTransactionMap.isEmpty()) {
twig("A.T.: setActiveTransactionsShown(false) because map is empty")
setActiveTransactionsShown(false)
return
}
val transactions = activeTransactionMap.entries.toTypedArray()
// primary is the last one that was inserted
val primaryEntry = transactions[transactions.size - 1]
updatePrimaryTransaction(primaryEntry.key, primaryEntry.value)
onContentRefreshComplete(false)
}
override fun onCancelledTooLate() {
snackbar = snackbar.showOk(view!!, "Oops! It was too late to cancel!")
}
// //
// View API // View API
// //
fun setContentViewShown(isShown: Boolean) { fun setContentViewShown(isShown: Boolean) {
with(binding.includeContent) { // with(binding.includeContent) {
groupEmptyViewItems.visibility = if (isShown) View.GONE else View.VISIBLE // groupEmptyViewItems.visibility = if (isShown) View.GONE else View.VISIBLE
groupContentViewItems.visibility = if (isShown) View.VISIBLE else View.GONE // groupContentViewItems.visibility = if (isShown) View.VISIBLE else View.GONE
} // }
toggleViews(!isShown) toggleViews(!isShown)
} }
@ -162,30 +217,6 @@ class HomeFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener, HomeP
} }
} }
//TODO: pull some of this logic into the presenter, particularly the part that deals with ZEC <-> USD price conversion
override fun updateBalance(old: Long, new: Long) {
val zecValue = new.convertZatoshiToZec()
setZecValue(zecValue.toZecString(3))
setUsdValue(zecValue.convertZecToUsd(SampleProperties.USD_PER_ZEC).toUsdString())
onContentRefreshComplete(new)
}
override fun setTransactions(transactions: List<WalletTransaction>) {
val recent = if(transactions.size > 12) transactions.subList(0, 12) else transactions
with (binding.includeContent.recyclerTransactions) {
(adapter as TransactionAdapter).submitList(recent)
postDelayed({
smoothScrollToPosition(0)
}, 100L)
}
if (recent.size != transactions.size) {
binding.includeContent.textTransactionHeaderSeeAll.visibility = View.VISIBLE
}
}
private fun onInitialLoadComplete() { private fun onInitialLoadComplete() {
val isEmpty = (binding.includeContent.recyclerTransactions?.adapter?.itemCount ?: 0).let { it == 0 } val isEmpty = (binding.includeContent.recyclerTransactions?.adapter?.itemCount ?: 0).let { it == 0 }
twig("onInitialLoadComplete and isEmpty == $isEmpty") twig("onInitialLoadComplete and isEmpty == $isEmpty")
@ -196,25 +227,6 @@ class HomeFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener, HomeP
setRefreshAnimationPlaying(false).also { twig("refresh false from onInitialLoadComplete") } setRefreshAnimationPlaying(false).also { twig("refresh false from onInitialLoadComplete") }
} }
override fun setActiveTransactions(activeTransactionMap: Map<ActiveTransaction, TransactionState>) {
if (activeTransactionMap.isEmpty()) {
twig("A.T.: setActiveTransactionsShown(false) because map is empty")
setActiveTransactionsShown(false)
return
}
val transactions = activeTransactionMap.entries.toTypedArray()
// primary is the last one that was inserted
val primaryEntry = transactions[transactions.size - 1]
updatePrimaryTransaction(primaryEntry.key, primaryEntry.value)
// TODO: update remaining transactions
}
override fun onCancelledTooLate() {
snackbar = snackbar.showOk(view!!, "Oops! It was too late to cancel!")
}
private fun updatePrimaryTransaction(transaction: ActiveTransaction, transactionState: TransactionState) { private fun updatePrimaryTransaction(transaction: ActiveTransaction, transactionState: TransactionState) {
twig("setting transaction state to ${transactionState::class.simpleName}") twig("setting transaction state to ${transactionState::class.simpleName}")
@ -294,7 +306,7 @@ class HomeFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener, HomeP
// //
// Private View API // Internal View Logic
// //
private fun setActiveTransactionsShown(isShown: Boolean, delay: Long = 0L) { private fun setActiveTransactionsShown(isShown: Boolean, delay: Long = 0L) {
@ -329,6 +341,7 @@ class HomeFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener, HomeP
setColorSchemeColors(R.color.zcashBlack.toAppColor()) setColorSchemeColors(R.color.zcashBlack.toAppColor())
setProgressBackgroundColorSchemeColor(R.color.zcashYellow.toAppColor()) setProgressBackgroundColorSchemeColor(R.color.zcashYellow.toAppColor())
} }
maxTransactionsShown = calculateMaxTransactions()
// hide content // hide content
setContentViewShown(false) setContentViewShown(false)
@ -336,6 +349,10 @@ class HomeFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener, HomeP
setRefreshAnimationPlaying(true).also { twig("refresh true from init") } setRefreshAnimationPlaying(true).also { twig("refresh true from init") }
} }
private fun calculateMaxTransactions(): Int {
return 12 //TODO: measure the screen and get optimal number for this device
}
// initialize the stuff that is temporary and needs to go ASAP // initialize the stuff that is temporary and needs to go ASAP
private fun initTemp() { private fun initTemp() {
@ -407,14 +424,13 @@ class HomeFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener, HomeP
* If the balance changes from zero, the wallet is no longer empty so hide the empty view. * If the balance changes from zero, the wallet is no longer empty so hide the empty view.
* But don't do either of these things if the situation has not changed. * But don't do either of these things if the situation has not changed.
*/ */
private fun onContentRefreshComplete(value: Long) { private fun onContentRefreshComplete(isEmpty: Boolean) {
val isEmpty = value <= 0L
// wasEmpty isn't enough info. it must be considered along with whether these views were ever initialized // wasEmpty isn't enough info. it must be considered along with whether these views were ever initialized
val wasEmpty = binding.includeContent.groupEmptyViewItems.visibility == View.VISIBLE val wasEmpty = binding.includeContent.groupEmptyViewItems.visibility == View.VISIBLE
// situation has changed when we weren't initialized but now we have a balance or emptiness has changed // situation has changed when we weren't initialized but now we have a balance or emptiness has changed
val situationHasChanged = !viewsInitialized || (isEmpty != wasEmpty) val situationHasChanged = !viewsInitialized || (isEmpty != wasEmpty)
twig("updateEmptyViews called with value: $value initialized: $viewsInitialized isEmpty: $isEmpty wasEmpty: $wasEmpty") twig("onContentRefreshComplete called initialized: $viewsInitialized isEmpty: $isEmpty wasEmpty: $wasEmpty")
if (situationHasChanged) { if (situationHasChanged) {
twig("The situation has changed! toggling views!") twig("The situation has changed! toggling views!")
setContentViewShown(!isEmpty) setContentViewShown(!isEmpty)
@ -464,6 +480,7 @@ class HomeFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener, HomeP
private inner class Ticker : Runnable { private inner class Ticker : Runnable {
override fun run() { override fun run() {
if (mainActivity == null) return
binding.includeContent.recyclerTransactions.apply { binding.includeContent.recyclerTransactions.apply {
if ((adapter?.itemCount ?: 0) > 0) { if ((adapter?.itemCount ?: 0) > 0) {
adapter?.notifyDataSetChanged() adapter?.notifyDataSetChanged()
@ -624,14 +641,11 @@ class HomeFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener, HomeP
} }
} }
@Module @Module
abstract class HomeFragmentModule { abstract class HomeFragmentModule {
@ContributesAndroidInjector @FragmentScope
@ContributesAndroidInjector(modules = [HomePresenterModule::class])
abstract fun contributeHomeFragment(): HomeFragment abstract fun contributeHomeFragment(): HomeFragment
@Binds
@Singleton
abstract fun providePresenter(homePresenter: HomePresenter): Presenter
} }

View File

@ -28,6 +28,8 @@ abstract class ProgressFragment(
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState) super.onActivityCreated(savedInstanceState)
// construct here rather than inject to make scoping easier
// so that we reduce the chances of updating progress on the wrong fragment
progressPresenter = ProgressPresenter(this, synchronizer) progressPresenter = ProgressPresenter(this, synchronizer)
} }

View File

@ -23,15 +23,13 @@ import cash.z.android.wallet.R
import cash.z.android.wallet.databinding.FragmentSendBinding import cash.z.android.wallet.databinding.FragmentSendBinding
import cash.z.android.wallet.extention.* import cash.z.android.wallet.extention.*
import cash.z.android.wallet.sample.SampleProperties import cash.z.android.wallet.sample.SampleProperties
import cash.z.android.wallet.ui.presenter.Presenter
import cash.z.android.wallet.ui.presenter.SendPresenter import cash.z.android.wallet.ui.presenter.SendPresenter
import cash.z.android.wallet.ui.presenter.SendPresenterModule
import cash.z.wallet.sdk.ext.convertZatoshiToZecString import cash.z.wallet.sdk.ext.convertZatoshiToZecString
import dagger.Binds
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton
/** /**
* Fragment for sending Zcash. * Fragment for sending Zcash.
@ -386,10 +384,6 @@ class SendFragment : BaseFragment(), SendPresenter.SendView, ScanFragment.Barcod
@Module @Module
abstract class SendFragmentModule { abstract class SendFragmentModule {
@ContributesAndroidInjector @ContributesAndroidInjector(modules = [SendPresenterModule::class])
abstract fun contributeSendFragment(): SendFragment abstract fun contributeSendFragment(): SendFragment
@Binds
@Singleton
abstract fun providePresenter(sendPresenter: SendPresenter): Presenter
} }

View File

@ -6,6 +6,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.view.doOnPreDraw import androidx.core.view.doOnPreDraw
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.navigation.NavOptions
import androidx.transition.TransitionInflater import androidx.transition.TransitionInflater
import cash.z.android.wallet.R import cash.z.android.wallet.R
import cash.z.android.wallet.databinding.FragmentSyncBinding import cash.z.android.wallet.databinding.FragmentSyncBinding
@ -43,7 +44,11 @@ class SyncFragment : ProgressFragment(R.id.progress_sync) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
postponeEnterTransition() postponeEnterTransition()
binding.buttonNext.setOnClickListener { binding.buttonNext.setOnClickListener {
mainActivity?.navController?.navigate(R.id.nav_home_fragment) mainActivity?.navController?.navigate(R.id.action_sync_fragment_to_home_fragment,
null,
NavOptions.Builder().setPopUpTo(R.id.mobile_navigation, true).build(),
null
)
} }
binding.progressSync.visibility = View.INVISIBLE binding.progressSync.visibility = View.INVISIBLE
binding.textProgressSync.visibility = View.INVISIBLE binding.textProgressSync.visibility = View.INVISIBLE

View File

@ -5,34 +5,16 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.databinding.DataBindingUtil import androidx.databinding.DataBindingUtil
import androidx.navigation.NavOptions
import androidx.navigation.fragment.FragmentNavigatorExtras import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.transition.TransitionInflater
import cash.z.android.wallet.BuildConfig
import cash.z.android.wallet.R import cash.z.android.wallet.R
import cash.z.android.wallet.databinding.FragmentWelcomeBinding import cash.z.android.wallet.databinding.FragmentWelcomeBinding
import dagger.Module import dagger.Module
import dagger.android.ContributesAndroidInjector import dagger.android.ContributesAndroidInjector
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import android.graphics.Bitmap
import android.R.attr.top
import android.R.attr.left
import android.graphics.RectF
import android.os.Parcelable
import androidx.core.app.ActivityCompat.setExitSharedElementCallback
import android.graphics.Canvas
import android.graphics.Matrix
import android.util.Log
import androidx.core.app.SharedElementCallback
import androidx.transition.TransitionInflater
import cash.z.android.wallet.BuildConfig
import cash.z.android.wallet.ui.presenter.Presenter
import cash.z.android.wallet.ui.presenter.ProgressPresenter
import dagger.Binds
import dagger.BindsInstance
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Singleton
class WelcomeFragment : ProgressFragment(R.id.progress_welcome) { class WelcomeFragment : ProgressFragment(R.id.progress_welcome) {
@ -109,7 +91,9 @@ class WelcomeFragment : ProgressFragment(R.id.progress_welcome) {
private suspend fun onNext() = coroutineScope { private suspend fun onNext() = coroutineScope {
if (mainActivity != null) { if (mainActivity != null) {
val isFirstRun = mainActivity!!.synchronizer.isFirstRun() val isFirstRun = mainActivity!!.synchronizer.isFirstRun()
val destination = if (isFirstRun) R.id.nav_firstrun_fragment else R.id.nav_sync_fragment val destination =
if (isFirstRun) R.id.action_welcome_fragment_to_firstrun_fragment
else R.id.action_welcome_fragment_to_sync_fragment
// var extras = with(binding) { // var extras = with(binding) {
// listOf(progressWelcome, textProgressWelcome) // listOf(progressWelcome, textProgressWelcome)
@ -119,10 +103,11 @@ class WelcomeFragment : ProgressFragment(R.id.progress_welcome) {
val extras = FragmentNavigatorExtras( val extras = FragmentNavigatorExtras(
binding.progressWelcome to binding.progressWelcome.transitionName binding.progressWelcome to binding.progressWelcome.transitionName
) )
mainActivity?.navController?.navigate( mainActivity?.navController?.navigate(
destination, destination,
null, null,
null, NavOptions.Builder().setPopUpTo(R.id.mobile_navigation, true).build(),
extras extras
) )
} }

View File

@ -1,10 +1,13 @@
package cash.z.android.wallet.ui.presenter package cash.z.android.wallet.ui.presenter
import cash.z.android.wallet.di.annotation.FragmentScope
import cash.z.android.wallet.ui.fragment.HistoryFragment import cash.z.android.wallet.ui.fragment.HistoryFragment
import cash.z.android.wallet.ui.presenter.Presenter.PresenterView import cash.z.android.wallet.ui.presenter.Presenter.PresenterView
import cash.z.wallet.sdk.dao.WalletTransaction import cash.z.wallet.sdk.dao.WalletTransaction
import cash.z.wallet.sdk.data.Synchronizer import cash.z.wallet.sdk.data.Synchronizer
import cash.z.wallet.sdk.data.twig import cash.z.wallet.sdk.data.twig
import dagger.Binds
import dagger.Module
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -16,23 +19,24 @@ import kotlin.coroutines.CoroutineContext
class HistoryPresenter @Inject constructor( class HistoryPresenter @Inject constructor(
private val view: HistoryFragment, private val view: HistoryFragment,
private var synchronizer: Synchronizer private var synchronizer: Synchronizer
) : Presenter, CoroutineScope { ) : Presenter {
private val job = Job() private var job: Job? = null
override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job
interface HistoryView : PresenterView { interface HistoryView : PresenterView {
fun setTransactions(transactions: List<WalletTransaction>) fun setTransactions(transactions: List<WalletTransaction>)
} }
override suspend fun start() { override suspend fun start() {
job?.cancel()
job = Job()
twig("historyPresenter starting!") twig("historyPresenter starting!")
launchTransactionBinder(synchronizer.allTransactions()) view.launchTransactionBinder(synchronizer.allTransactions())
} }
override fun stop() { override fun stop() {
twig("historyPresenter stopping!") twig("historyPresenter stopping!")
job.cancel() job?.cancel()?.also { job = null }
} }
private fun CoroutineScope.launchTransactionBinder(channel: ReceiveChannel<List<WalletTransaction>>) = launch { private fun CoroutineScope.launchTransactionBinder(channel: ReceiveChannel<List<WalletTransaction>>) = launch {
@ -58,3 +62,10 @@ class HistoryPresenter @Inject constructor(
} }
@Module
abstract class HistoryPresenterModule {
@Binds
@FragmentScope
abstract fun providePresenter(historyPresenter: HistoryPresenter): Presenter
}

View File

@ -1,24 +1,23 @@
package cash.z.android.wallet.ui.presenter package cash.z.android.wallet.ui.presenter
import android.util.Log import cash.z.android.wallet.di.annotation.FragmentScope
import cash.z.android.wallet.ZcashWalletApplication
import cash.z.android.wallet.ui.fragment.HomeFragment import cash.z.android.wallet.ui.fragment.HomeFragment
import cash.z.android.wallet.ui.presenter.Presenter.PresenterView import cash.z.android.wallet.ui.presenter.Presenter.PresenterView
import cash.z.wallet.sdk.dao.WalletTransaction import cash.z.wallet.sdk.dao.WalletTransaction
import cash.z.wallet.sdk.data.* import cash.z.wallet.sdk.data.*
import dagger.Binds
import dagger.Module
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.channels.ReceiveChannel
import javax.inject.Inject import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
class HomePresenter @Inject constructor( class HomePresenter @Inject constructor(
private val view: HomeFragment, private val view: HomeFragment,
private val synchronizer: Synchronizer private val synchronizer: Synchronizer
) : Presenter, CoroutineScope { ) : Presenter {
private val job = Job() private var job: Job? = null
override val coroutineContext: CoroutineContext get() = Dispatchers.IO + job
interface HomeView : PresenterView { interface HomeView : PresenterView {
fun setTransactions(transactions: List<WalletTransaction>) fun setTransactions(transactions: List<WalletTransaction>)
@ -28,15 +27,19 @@ class HomePresenter @Inject constructor(
} }
override suspend fun start() { override suspend fun start() {
twig("homePresenter starting!") job?.cancel()
launchBalanceBinder(synchronizer.balance()) job = Job()
launchTransactionBinder(synchronizer.allTransactions()) twig("homePresenter starting! from ${this.hashCode()}")
launchActiveTransactionMonitor(synchronizer.activeTransactions()) with(view) {
launchBalanceBinder(synchronizer.balance())
launchTransactionBinder(synchronizer.allTransactions())
launchActiveTransactionMonitor(synchronizer.activeTransactions())
}
} }
override fun stop() { override fun stop() {
twig("homePresenter stopping!") twig("homePresenter stopping!")
job.cancel() job?.cancel()?.also { job = null }
} }
private fun CoroutineScope.launchBalanceBinder(channel: ReceiveChannel<Long>) = launch { private fun CoroutineScope.launchBalanceBinder(channel: ReceiveChannel<Long>) = launch {
@ -71,20 +74,20 @@ class HomePresenter @Inject constructor(
// View Callbacks on Main Thread // View Callbacks on Main Thread
// //
private fun bind(old: Long?, new: Long) = onMain { private fun bind(old: Long?, new: Long) {
twig("binding balance of $new") twig("binding balance of $new")
view.updateBalance(old ?: 0L, new) view.updateBalance(old ?: 0L, new)
} }
private fun bind(transactions: List<WalletTransaction>) = onMain { private fun bind(transactions: List<WalletTransaction>) {
twig("binding ${transactions.size} walletTransactions") twig("binding ${transactions.size} walletTransactions")
view.setTransactions(transactions.sortedByDescending { view.setTransactions(transactions.sortedByDescending {
if (!it.isMined && it.isSend) Long.MAX_VALUE else it.timeInSeconds if (!it.isMined && it.isSend) Long.MAX_VALUE else it.timeInSeconds
}) })
} }
private fun bind(activeTransactionMap: Map<ActiveTransaction, TransactionState>) = onMain { private fun bind(activeTransactionMap: Map<ActiveTransaction, TransactionState>) {
twig("binding a.t. map of size ${activeTransactionMap.size}") twig("binding a.t. map of size ${activeTransactionMap.size}")
if (activeTransactionMap.isNotEmpty()) view.setActiveTransactions(activeTransactionMap) if (activeTransactionMap.isNotEmpty()) view.setActiveTransactions(activeTransactionMap)
} }
@ -97,13 +100,11 @@ class HomePresenter @Inject constructor(
} }
} }
private fun onMain(block: () -> Unit) = launch {
withContext(Main) {
twig("running task on main thread - start ${coroutineContext[Job]} | ${coroutineContext[CoroutineName]}")
block()
twig("running task on main thread - complete")
}
}
} }
@Module
abstract class HomePresenterModule {
@Binds
@FragmentScope
abstract fun providePresenter(homePresenter: HomePresenter): Presenter
}

View File

@ -12,28 +12,31 @@ import kotlin.coroutines.CoroutineContext
class ProgressPresenter @Inject constructor( class ProgressPresenter @Inject constructor(
private val view: ProgressView, private val view: ProgressView,
private var synchronizer: Synchronizer private var synchronizer: Synchronizer
) : Presenter, CoroutineScope { ) : Presenter {
private val job = Job() private var job: Job? = null
override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job
interface ProgressView : PresenterView { interface ProgressView : PresenterView {
fun showProgress(progress: Int) fun showProgress(progress: Int)
} }
// //
// LifeCycle // LifeCycle
// //
override suspend fun start() { override suspend fun start() {
job?.cancel()
job = Job()
Twig.sprout("ProgressPresenter") Twig.sprout("ProgressPresenter")
twig("starting") twig("starting")
launchProgressMonitor(synchronizer.progress()) view.launchProgressMonitor(synchronizer.progress())
} }
override fun stop() { override fun stop() {
Twig.clip("ProgressPresenter") Twig.clip("ProgressPresenter")
twig("stopping") twig("stopping")
job?.cancel()?.also { job = null }
} }
private fun CoroutineScope.launchProgressMonitor(channel: ReceiveChannel<Int>) = launch { private fun CoroutineScope.launchProgressMonitor(channel: ReceiveChannel<Int>) = launch {
@ -46,7 +49,7 @@ class ProgressPresenter @Inject constructor(
twig("progress monitor exiting!") twig("progress monitor exiting!")
} }
private fun bind(progress: Int) = launch { private fun bind(progress: Int) = view.launch {
twig("binding progress of $progress on thread ${Thread.currentThread().name}!") twig("binding progress of $progress on thread ${Thread.currentThread().name}!")
view.showProgress(progress) view.showProgress(progress)
} }

View File

@ -1,6 +1,7 @@
package cash.z.android.wallet.ui.presenter package cash.z.android.wallet.ui.presenter
import cash.z.android.wallet.R import cash.z.android.wallet.R
import cash.z.android.wallet.di.annotation.FragmentScope
import cash.z.android.wallet.extention.toAppString import cash.z.android.wallet.extention.toAppString
import cash.z.android.wallet.sample.SampleProperties import cash.z.android.wallet.sample.SampleProperties
import cash.z.android.wallet.ui.fragment.SendFragment import cash.z.android.wallet.ui.fragment.SendFragment
@ -9,6 +10,8 @@ import cash.z.wallet.sdk.data.Synchronizer
import cash.z.wallet.sdk.data.twig import cash.z.wallet.sdk.data.twig
import cash.z.wallet.sdk.data.Twig import cash.z.wallet.sdk.data.Twig
import cash.z.wallet.sdk.ext.* import cash.z.wallet.sdk.ext.*
import dagger.Binds
import dagger.Module
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -61,9 +64,9 @@ class SendPresenter @Inject constructor(
twig("sendPresenter starting!") twig("sendPresenter starting!")
// set the currency to zec and update the view, initializing everything to zero // set the currency to zec and update the view, initializing everything to zero
inputToggleCurrency() inputToggleCurrency()
with(view) { balanceJob?.cancel()
balanceJob = launchBalanceBinder(synchronizer.balance()) balanceJob = Job()
} balanceJob = view.launchBalanceBinder(synchronizer.balance())
} }
override fun stop() { override fun stop() {
@ -347,4 +350,12 @@ class SendPresenter @Inject constructor(
val toAddress: String = "", val toAddress: String = "",
val memo: String = "" val memo: String = ""
) )
}
@Module
abstract class SendPresenterModule {
@Binds
@FragmentScope
abstract fun providePresenter(sendPresenter: SendPresenter): Presenter
} }

View File

@ -1,17 +1,4 @@
<?xml version="1.0" encoding="utf-8"?><!-- <?xml version="1.0" encoding="utf-8"?>
Copyright (C) 2018 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">

View File

@ -12,12 +12,18 @@
tools:layout="@layout/fragment_welcome"> tools:layout="@layout/fragment_welcome">
<action <action
android:id="@+id/action_welcome_fragment_to_firstrun_fragment" android:id="@+id/action_welcome_fragment_to_firstrun_fragment"
app:popUpTo="@navigation/mobile_navigation"
app:popUpToInclusive="true"
app:destination="@id/nav_firstrun_fragment" /> app:destination="@id/nav_firstrun_fragment" />
<action <action
android:id="@+id/action_welcome_fragment_to_sync_fragment" android:id="@+id/action_welcome_fragment_to_sync_fragment"
app:popUpTo="@navigation/mobile_navigation"
app:popUpToInclusive="true"
app:destination="@id/nav_sync_fragment" /> app:destination="@id/nav_sync_fragment" />
<action <action
android:id="@+id/action_welcome_fragment_to_home_fragment" android:id="@+id/action_welcome_fragment_to_home_fragment"
app:popUpTo="@navigation/mobile_navigation"
app:popUpToInclusive="true"
app:destination="@id/nav_home_fragment" /> app:destination="@id/nav_home_fragment" />
</fragment> </fragment>
<fragment <fragment
@ -27,6 +33,8 @@
tools:layout="@layout/fragment_firstrun"> tools:layout="@layout/fragment_firstrun">
<action <action
android:id="@+id/action_firstrun_fragment_to_sync_fragment" android:id="@+id/action_firstrun_fragment_to_sync_fragment"
app:popUpTo="@navigation/mobile_navigation"
app:popUpToInclusive="true"
app:destination="@id/nav_sync_fragment" /> app:destination="@id/nav_sync_fragment" />
</fragment> </fragment>
<fragment <fragment
@ -36,6 +44,8 @@
tools:layout="@layout/fragment_sync"> tools:layout="@layout/fragment_sync">
<action <action
android:id="@+id/action_sync_fragment_to_home_fragment" android:id="@+id/action_sync_fragment_to_home_fragment"
app:popUpTo="@navigation/mobile_navigation"
app:popUpToInclusive="true"
app:destination="@id/nav_home_fragment" /> app:destination="@id/nav_home_fragment" />
</fragment> </fragment>
<fragment <fragment

View File

@ -17,7 +17,8 @@ internal object SynchronizerModule {
// const val MOCK_LOAD_DURATION = 3_000L // const val MOCK_LOAD_DURATION = 3_000L
const val MOCK_LOAD_DURATION = 12_000L const val MOCK_LOAD_DURATION = 12_000L
const val MOCK_TX_INTERVAL = 20_000L // const val MOCK_TX_INTERVAL = 20_000L
const val MOCK_TX_INTERVAL = 5_000L
const val MOCK_ACTIVE_TX_STATE_CHANGE_INTERVAL = 7_000L const val MOCK_ACTIVE_TX_STATE_CHANGE_INTERVAL = 7_000L
const val MOCK_IS_FIRST_RUN: Boolean = true const val MOCK_IS_FIRST_RUN: Boolean = true

View File

@ -8,7 +8,7 @@ buildscript {
'dagger': '2.19', 'dagger': '2.19',
'kotlin': '1.3.11', 'kotlin': '1.3.11',
'coroutines': '1.1.0', 'coroutines': '1.1.0',
'navigation': '1.0.0-alpha07' 'navigation': '1.0.0-rc01'
] ]
ext.deps = [ ext.deps = [
'androidx': [ 'androidx': [