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
@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
@Scope
annotation class ApplicationScoped
@Retention(AnnotationRetention.SOURCE)
annotation class FragmentScope

View File

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

View File

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

View File

@ -6,6 +6,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.core.view.doOnPreDraw
import androidx.databinding.DataBindingUtil
import androidx.navigation.NavOptions
import androidx.transition.Transition
import androidx.transition.TransitionInflater
import cash.z.android.wallet.R
@ -38,7 +39,12 @@ class FirstrunFragment : ProgressFragment(R.id.progress_firstrun), Transition.Tr
super.onViewCreated(view, savedInstanceState)
postponeEnterTransition()
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.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.ui.adapter.TransactionAdapter
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.wallet.sdk.dao.WalletTransaction
import dagger.Binds
import dagger.Module
import dagger.android.ContributesAndroidInjector
import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Singleton
class HistoryFragment : BaseFragment(), HistoryPresenter.HistoryView {
@ -73,10 +71,6 @@ class HistoryFragment : BaseFragment(), HistoryPresenter.HistoryView {
@Module
abstract class HistoryFragmentModule {
@ContributesAndroidInjector
@ContributesAndroidInjector(modules = [HistoryPresenterModule::class])
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 cash.z.android.wallet.R
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.sample.SampleProperties
import cash.z.android.wallet.ui.adapter.TransactionAdapter
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.util.AlternatingRowColorDecoration
import cash.z.android.wallet.ui.util.LottieLooper
@ -43,7 +45,6 @@ import dagger.Module
import dagger.android.ContributesAndroidInjector
import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.random.Random
import kotlin.random.nextLong
@ -59,6 +60,7 @@ class HomeFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener, HomeP
private lateinit var binding: FragmentHomeBinding
private lateinit var zcashLogoAnimation: LottieLooper
private var maxTransactionsShown: Int = 12
private var snackbar: Snackbar? = null
private var viewsInitialized = false
@ -66,6 +68,11 @@ class HomeFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener, HomeP
private var clock: Handler = Handler()
private val tickIfNeeded = Ticker()
//
// LifeCycle
//
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
@ -89,7 +96,6 @@ class HomeFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener, HomeP
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
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
//
fun setContentViewShown(isShown: Boolean) {
with(binding.includeContent) {
groupEmptyViewItems.visibility = if (isShown) View.GONE else View.VISIBLE
groupContentViewItems.visibility = if (isShown) View.VISIBLE else View.GONE
}
// with(binding.includeContent) {
// groupEmptyViewItems.visibility = if (isShown) View.GONE else View.VISIBLE
// groupContentViewItems.visibility = if (isShown) View.VISIBLE else View.GONE
// }
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() {
val isEmpty = (binding.includeContent.recyclerTransactions?.adapter?.itemCount ?: 0).let { it == 0 }
twig("onInitialLoadComplete and isEmpty == $isEmpty")
@ -196,25 +227,6 @@ class HomeFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener, HomeP
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) {
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) {
@ -329,6 +341,7 @@ class HomeFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener, HomeP
setColorSchemeColors(R.color.zcashBlack.toAppColor())
setProgressBackgroundColorSchemeColor(R.color.zcashYellow.toAppColor())
}
maxTransactionsShown = calculateMaxTransactions()
// hide content
setContentViewShown(false)
@ -336,6 +349,10 @@ class HomeFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener, HomeP
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
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.
* But don't do either of these things if the situation has not changed.
*/
private fun onContentRefreshComplete(value: Long) {
val isEmpty = value <= 0L
private fun onContentRefreshComplete(isEmpty: Boolean) {
// 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
// situation has changed when we weren't initialized but now we have a balance or emptiness has changed
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) {
twig("The situation has changed! toggling views!")
setContentViewShown(!isEmpty)
@ -464,6 +480,7 @@ class HomeFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener, HomeP
private inner class Ticker : Runnable {
override fun run() {
if (mainActivity == null) return
binding.includeContent.recyclerTransactions.apply {
if ((adapter?.itemCount ?: 0) > 0) {
adapter?.notifyDataSetChanged()
@ -624,14 +641,11 @@ class HomeFragment : BaseFragment(), SwipeRefreshLayout.OnRefreshListener, HomeP
}
}
@Module
abstract class HomeFragmentModule {
@ContributesAndroidInjector
@FragmentScope
@ContributesAndroidInjector(modules = [HomePresenterModule::class])
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?) {
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)
}

View File

@ -23,15 +23,13 @@ import cash.z.android.wallet.R
import cash.z.android.wallet.databinding.FragmentSendBinding
import cash.z.android.wallet.extention.*
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.SendPresenterModule
import cash.z.wallet.sdk.ext.convertZatoshiToZecString
import dagger.Binds
import dagger.Module
import dagger.android.ContributesAndroidInjector
import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Singleton
/**
* Fragment for sending Zcash.
@ -386,10 +384,6 @@ class SendFragment : BaseFragment(), SendPresenter.SendView, ScanFragment.Barcod
@Module
abstract class SendFragmentModule {
@ContributesAndroidInjector
@ContributesAndroidInjector(modules = [SendPresenterModule::class])
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 androidx.core.view.doOnPreDraw
import androidx.databinding.DataBindingUtil
import androidx.navigation.NavOptions
import androidx.transition.TransitionInflater
import cash.z.android.wallet.R
import cash.z.android.wallet.databinding.FragmentSyncBinding
@ -43,7 +44,11 @@ class SyncFragment : ProgressFragment(R.id.progress_sync) {
super.onViewCreated(view, savedInstanceState)
postponeEnterTransition()
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.textProgressSync.visibility = View.INVISIBLE

View File

@ -5,34 +5,16 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.navigation.NavOptions
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.databinding.FragmentWelcomeBinding
import dagger.Module
import dagger.android.ContributesAndroidInjector
import kotlinx.coroutines.coroutineScope
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) {
@ -109,7 +91,9 @@ class WelcomeFragment : ProgressFragment(R.id.progress_welcome) {
private suspend fun onNext() = coroutineScope {
if (mainActivity != null) {
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) {
// listOf(progressWelcome, textProgressWelcome)
@ -119,10 +103,11 @@ class WelcomeFragment : ProgressFragment(R.id.progress_welcome) {
val extras = FragmentNavigatorExtras(
binding.progressWelcome to binding.progressWelcome.transitionName
)
mainActivity?.navController?.navigate(
destination,
null,
null,
NavOptions.Builder().setPopUpTo(R.id.mobile_navigation, true).build(),
extras
)
}

View File

@ -1,10 +1,13 @@
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.presenter.Presenter.PresenterView
import cash.z.wallet.sdk.dao.WalletTransaction
import cash.z.wallet.sdk.data.Synchronizer
import cash.z.wallet.sdk.data.twig
import dagger.Binds
import dagger.Module
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -16,23 +19,24 @@ import kotlin.coroutines.CoroutineContext
class HistoryPresenter @Inject constructor(
private val view: HistoryFragment,
private var synchronizer: Synchronizer
) : Presenter, CoroutineScope {
) : Presenter {
private val job = Job()
override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job
private var job: Job? = null
interface HistoryView : PresenterView {
fun setTransactions(transactions: List<WalletTransaction>)
}
override suspend fun start() {
job?.cancel()
job = Job()
twig("historyPresenter starting!")
launchTransactionBinder(synchronizer.allTransactions())
view.launchTransactionBinder(synchronizer.allTransactions())
}
override fun stop() {
twig("historyPresenter stopping!")
job.cancel()
job?.cancel()?.also { job = null }
}
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
import android.util.Log
import cash.z.android.wallet.ZcashWalletApplication
import cash.z.android.wallet.di.annotation.FragmentScope
import cash.z.android.wallet.ui.fragment.HomeFragment
import cash.z.android.wallet.ui.presenter.Presenter.PresenterView
import cash.z.wallet.sdk.dao.WalletTransaction
import cash.z.wallet.sdk.data.*
import dagger.Binds
import dagger.Module
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.channels.ReceiveChannel
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
class HomePresenter @Inject constructor(
private val view: HomeFragment,
private val synchronizer: Synchronizer
) : Presenter, CoroutineScope {
) : Presenter {
private val job = Job()
override val coroutineContext: CoroutineContext get() = Dispatchers.IO + job
private var job: Job? = null
interface HomeView : PresenterView {
fun setTransactions(transactions: List<WalletTransaction>)
@ -28,15 +27,19 @@ class HomePresenter @Inject constructor(
}
override suspend fun start() {
twig("homePresenter starting!")
launchBalanceBinder(synchronizer.balance())
launchTransactionBinder(synchronizer.allTransactions())
launchActiveTransactionMonitor(synchronizer.activeTransactions())
job?.cancel()
job = Job()
twig("homePresenter starting! from ${this.hashCode()}")
with(view) {
launchBalanceBinder(synchronizer.balance())
launchTransactionBinder(synchronizer.allTransactions())
launchActiveTransactionMonitor(synchronizer.activeTransactions())
}
}
override fun stop() {
twig("homePresenter stopping!")
job.cancel()
job?.cancel()?.also { job = null }
}
private fun CoroutineScope.launchBalanceBinder(channel: ReceiveChannel<Long>) = launch {
@ -71,20 +74,20 @@ class HomePresenter @Inject constructor(
// 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")
view.updateBalance(old ?: 0L, new)
}
private fun bind(transactions: List<WalletTransaction>) = onMain {
private fun bind(transactions: List<WalletTransaction>) {
twig("binding ${transactions.size} walletTransactions")
view.setTransactions(transactions.sortedByDescending {
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}")
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(
private val view: ProgressView,
private var synchronizer: Synchronizer
) : Presenter, CoroutineScope {
) : Presenter {
private val job = Job()
override val coroutineContext: CoroutineContext get() = Dispatchers.Main + job
private var job: Job? = null
interface ProgressView : PresenterView {
fun showProgress(progress: Int)
}
//
// LifeCycle
//
override suspend fun start() {
job?.cancel()
job = Job()
Twig.sprout("ProgressPresenter")
twig("starting")
launchProgressMonitor(synchronizer.progress())
view.launchProgressMonitor(synchronizer.progress())
}
override fun stop() {
Twig.clip("ProgressPresenter")
twig("stopping")
job?.cancel()?.also { job = null }
}
private fun CoroutineScope.launchProgressMonitor(channel: ReceiveChannel<Int>) = launch {
@ -46,7 +49,7 @@ class ProgressPresenter @Inject constructor(
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}!")
view.showProgress(progress)
}

View File

@ -1,6 +1,7 @@
package cash.z.android.wallet.ui.presenter
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.sample.SampleProperties
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.ext.*
import dagger.Binds
import dagger.Module
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
@ -61,9 +64,9 @@ class SendPresenter @Inject constructor(
twig("sendPresenter starting!")
// set the currency to zec and update the view, initializing everything to zero
inputToggleCurrency()
with(view) {
balanceJob = launchBalanceBinder(synchronizer.balance())
}
balanceJob?.cancel()
balanceJob = Job()
balanceJob = view.launchBalanceBinder(synchronizer.balance())
}
override fun stop() {
@ -347,4 +350,12 @@ class SendPresenter @Inject constructor(
val toAddress: 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"?><!--
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.
-->
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">

View File

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

View File

@ -17,7 +17,8 @@ internal object SynchronizerModule {
// const val MOCK_LOAD_DURATION = 3_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_IS_FIRST_RUN: Boolean = true

View File

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