2018-11-12 10:38:37 -08:00
package cash.z.android.wallet.ui.fragment
2018-12-02 20:45:59 -08:00
import android.app.Activity
2018-11-12 10:38:37 -08:00
import android.os.Bundle
2018-12-07 09:14:54 -08:00
import android.text.SpannableString
import android.text.Spanned
2019-01-31 12:44:25 -08:00
import android.util.Log
2018-11-21 02:11:48 -08:00
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
2018-12-02 20:45:59 -08:00
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.IdRes
import androidx.annotation.StringRes
2018-12-07 16:27:57 -08:00
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
2019-02-01 08:10:43 -08:00
import androidx.transition.ChangeBounds
import androidx.transition.TransitionInflater
import androidx.transition.TransitionSet
2018-11-12 10:38:37 -08:00
import cash.z.android.wallet.R
2018-12-06 23:16:51 -08:00
import cash.z.android.wallet.extention.toAppColor
import cash.z.android.wallet.extention.toAppString
2018-12-10 08:38:03 -08:00
import cash.z.android.wallet.extention.tryIgnore
2018-11-13 15:51:40 -08:00
import cash.z.android.wallet.ui.activity.MainActivity
2018-12-07 16:27:57 -08:00
import cash.z.android.wallet.ui.adapter.TransactionAdapter
2018-12-30 17:37:43 -08:00
import cash.z.android.wallet.ui.presenter.HomePresenter
2018-12-30 18:41:47 -08:00
import cash.z.android.wallet.ui.util.AlternatingRowColorDecoration
2018-12-07 09:14:54 -08:00
import cash.z.android.wallet.ui.util.TopAlignedSpan
2018-12-07 16:27:57 -08:00
import cash.z.android.wallet.vo.WalletTransaction
2018-12-02 19:21:23 -08:00
import com.leinardi.android.speeddial.SpeedDialActionItem
2018-12-04 23:26:03 -08:00
import dagger.Module
import dagger.android.ContributesAndroidInjector
2018-12-07 16:27:57 -08:00
import kotlinx.android.synthetic.main.fragment_home.*
2018-12-10 08:38:03 -08:00
import kotlinx.android.synthetic.main.include_home_content.*
import kotlinx.android.synthetic.main.include_home_header.*
2018-12-30 17:37:43 -08:00
import kotlinx.coroutines.launch
2018-12-07 16:27:57 -08:00
import kotlin.random.Random
2019-01-31 14:47:37 -08:00
import com.google.android.material.snackbar.Snackbar
2018-11-12 10:38:37 -08:00
/ * *
2018-12-02 20:45:59 -08:00
* Fragment representing the home screen of the app . This is the screen most often seen by the user when launching the
* application .
2018-11-12 10:38:37 -08:00
* /
2019-01-29 10:08:33 -08:00
class HomeFragment : BaseFragment ( ) , HomePresenter . HomeView {
2018-11-21 02:11:48 -08:00
2018-12-30 17:37:43 -08:00
lateinit var homePresenter : HomePresenter
2018-12-30 18:41:47 -08:00
lateinit var transactionAdapter : TransactionAdapter
2019-01-31 12:44:25 -08:00
var viewsInitialized = false
2018-12-30 17:37:43 -08:00
2018-11-12 10:38:37 -08:00
override fun onCreateView (
inflater : LayoutInflater , container : ViewGroup ? ,
savedInstanceState : Bundle ?
) : View ? {
2019-01-31 12:44:25 -08:00
viewsInitialized = false
2019-02-01 19:35:45 -08:00
val enterTransitionSet = TransitionInflater . from ( mainActivity ) . inflateTransition ( R . transition . transition _zec _sent ) . apply {
2019-02-01 22:42:23 -08:00
duration = 3500L
2019-02-01 08:10:43 -08:00
}
2019-02-01 19:35:45 -08:00
2019-02-01 08:10:43 -08:00
this . sharedElementEnterTransition = enterTransitionSet
this . sharedElementReturnTransition = enterTransitionSet
2018-12-07 16:27:57 -08:00
return inflater . inflate ( R . layout . fragment _home , container , false )
2018-11-12 10:38:37 -08:00
}
2018-11-13 21:16:53 -08:00
override fun onViewCreated ( view : View , savedInstanceState : Bundle ? ) {
super . onViewCreated ( view , savedInstanceState )
2018-12-02 20:45:59 -08:00
( activity as MainActivity ) . let { mainActivity ->
2018-12-10 08:38:03 -08:00
mainActivity . setSupportActionBar ( home _toolbar )
2018-12-02 20:45:59 -08:00
mainActivity . setupNavigation ( )
mainActivity . supportActionBar ?. setTitle ( R . string . destination _title _home )
}
2019-02-01 19:35:45 -08:00
// background_active_transaction.isActivated = true
2018-12-10 13:24:38 -08:00
headerFullViews = arrayOf ( text _balance _usd , text _balance _includes _info , text _balance _zec , image _zec _symbol _balance _shadow , image _zec _symbol _balance )
headerEmptyViews = arrayOf ( text _balance _zec _info , text _balance _zec _empty , image _zec _symbol _balance _shadow _empty , image _zec _symbol _balance _empty )
2019-01-31 12:44:25 -08:00
// toggling determines visibility. hide it all.
headerFullViews . forEach { container _home _header . removeView ( it ) }
headerEmptyViews . forEach { container _home _header . removeView ( it ) }
group _empty _view _items . visibility = View . GONE
group _full _view _items . visibility = View . GONE
2019-02-01 19:35:45 -08:00
image _logo . setOnClickListener {
2019-02-01 08:10:43 -08:00
// forceRedraw()
// toggleViews(false)
2019-02-01 19:35:45 -08:00
background _active _transaction . isActivated = ! background _active _transaction . isActivated
}
2018-11-13 21:16:53 -08:00
}
2018-12-07 16:27:57 -08:00
override fun onResume ( ) {
super . onResume ( )
2019-01-31 12:44:25 -08:00
val isEmpty = ( recycler _transactions ?. adapter ?. itemCount ?: 0 ) . let { it == 0 }
// Log.e("TWIG-t", "Resuming and isEmpty == $isEmpty")
// toggleViews(isEmpty)
2018-12-30 17:37:43 -08:00
2019-01-29 10:08:33 -08:00
launch {
homePresenter . start ( )
}
2018-12-30 17:37:43 -08:00
}
override fun onPause ( ) {
super . onPause ( )
2019-01-29 10:08:33 -08:00
homePresenter . stop ( )
2018-12-07 16:27:57 -08:00
}
2018-12-02 19:21:23 -08:00
override fun onActivityCreated ( savedInstanceState : Bundle ? ) {
super . onActivityCreated ( savedInstanceState )
2019-01-29 10:08:33 -08:00
homePresenter = HomePresenter ( this , mainActivity . synchronizer )
2018-12-02 20:45:59 -08:00
initFab ( activity !! )
2018-12-07 16:27:57 -08:00
2018-12-30 18:41:47 -08:00
recycler _transactions . apply {
layoutManager = LinearLayoutManager ( activity , RecyclerView . VERTICAL , false )
adapter = TransactionAdapter ( ) . also { transactionAdapter = it }
addItemDecoration ( AlternatingRowColorDecoration ( ) )
}
2018-12-02 19:21:23 -08:00
}
2018-12-30 18:41:47 -08:00
//
// View API
//
// TODO: pull some of this logic into the presenter, particularly the part that deals with ZEC <-> USD price conversion
2019-01-29 10:08:33 -08:00
override fun updateBalance ( old : Long , new : Long ) {
2019-01-31 14:47:37 -08:00
//TODO: remove this kind of thing
snackbar ?. dismiss ( )
2019-01-31 12:44:25 -08:00
Log . e ( " TWIG-t " , " updating balance from $old to $new " )
val zecValue = new / 1 e8
swapEmptyViewsForBalance ( zecValue )
2018-12-30 18:41:47 -08:00
// TODO: animate the change in value
setZecValue ( zecValue )
2019-01-14 00:09:02 -08:00
setUsdValue ( MainActivity . USD _PER _ZEC * zecValue )
2018-12-30 18:41:47 -08:00
}
2019-01-31 12:44:25 -08:00
override fun setTransactions ( transactions : List < WalletTransaction > ) {
Log . e ( " TWIG-t " , " submitList called with ${transactions.size} transactions " )
transactionAdapter . submitList ( transactions )
2018-12-30 19:02:56 -08:00
recycler _transactions . smoothScrollToPosition ( 0 )
2018-12-30 18:41:47 -08:00
}
2019-01-31 14:47:37 -08:00
var snackbar : Snackbar ? = null
2019-01-29 10:08:33 -08:00
override fun showProgress ( progress : Int ) {
2019-01-31 14:47:37 -08:00
Log . e ( " TWIG " , " showing progress of $progress " )
// TODO: improve this with Lottie animation. but for now just use the empty view for downloading...
// var hasEmptyViews = group_empty_view_items.visibility == View.VISIBLE
// if(!viewsInitialized) toggleViews(true)
//
val message = if ( progress >= 100 ) " Download complete! Processing... \n (this may take a while) " else " Downloading blocks ( $progress %) "
// text_wallet_message.text = message
if ( snackbar == null && progress <= 50 ) {
snackbar = Snackbar . make ( view !! , " $message " , Snackbar . LENGTH _INDEFINITE )
. setAction ( " OK " ) {
snackbar ?. dismiss ( )
}
snackbar ?. show ( )
} else {
snackbar ?. setText ( message )
if ( progress == 100 && snackbar ?. isShownOrQueued != true ) snackbar ?. show ( )
}
2019-01-29 10:08:33 -08:00
}
2018-12-30 18:41:47 -08:00
//
// Private API
//
2018-12-02 20:45:59 -08:00
/ * *
* Initialize the Fab button and all its action items
*
* @param activity a helper parameter that forces this method to be called after the activity is created and not null
* /
private fun initFab ( activity : Activity ) {
2018-12-02 19:21:23 -08:00
val speedDial = sd _fab
val nav = ( activity as MainActivity ) . navController
2018-12-02 20:45:59 -08:00
HomeFab . values ( ) . forEach {
2018-12-06 23:16:51 -08:00
speedDial . addActionItem ( it . createItem ( ) )
2018-12-02 20:45:59 -08:00
}
2018-12-02 19:21:23 -08:00
speedDial . setOnActionSelectedListener { item ->
2018-12-02 20:45:59 -08:00
HomeFab . fromId ( item . id ) ?. destination ?. apply { nav . navigate ( this ) }
2018-12-02 19:21:23 -08:00
false
}
}
2018-12-06 23:16:51 -08:00
private val createItem : HomeFab . ( ) -> SpeedDialActionItem = {
SpeedDialActionItem . Builder ( id , icon )
. setFabBackgroundColor ( bgColor . toAppColor ( ) )
. setFabImageTintColor ( R . color . zcashWhite . toAppColor ( ) )
. setLabel ( label . toAppString ( ) )
. setLabelClickable ( true )
. create ( )
}
2018-12-30 17:56:50 -08:00
private fun setUsdValue ( value : Double ) {
2018-12-10 08:38:03 -08:00
val valueString = String . format ( " $ %,.2f" , value )
val hairSpace = " \u200A "
// val adjustedValue = "$$hairSpace$valueString"
val textSpan = SpannableString ( valueString )
textSpan . setSpan ( TopAlignedSpan ( ) , 0 , 2 , Spanned . SPAN _EXCLUSIVE _EXCLUSIVE )
textSpan . setSpan ( TopAlignedSpan ( ) , valueString . length - 3 , valueString . length , Spanned . SPAN _EXCLUSIVE _EXCLUSIVE )
2018-12-07 09:14:54 -08:00
text _balance _usd . text = textSpan
}
2018-12-30 17:56:50 -08:00
private fun setZecValue ( value : Double ) {
2018-12-10 08:38:03 -08:00
text _balance _zec . text = if ( value == 0.0 ) " 0 " else String . format ( " %.3f " , value )
// // bugfix: there is a bug in motionlayout that causes text to flicker as it is resized because the last character doesn't fit. Padding both sides with a thin space works around this bug.
// val hairSpace = "\u200A"
// val adjustedValue = "$hairSpace$valueString$hairSpace"
// text_balance_zec.text = adjustedValue
}
2019-01-31 12:44:25 -08:00
/ * *
* If the balance goes to zero , the wallet is now empty so show 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 .
* /
private fun swapEmptyViewsForBalance ( value : Double ) {
val isEmpty = value <= 0.0
// wasEmpty isn't enough info. it must be considered along with whether these views were ever initialized
2018-12-30 17:56:50 -08:00
val wasEmpty = group _empty _view _items . visibility == View . VISIBLE
2019-01-31 12:44:25 -08:00
// situation has changed when we weren't initialized but now we have a balance or emptiness has changed
val situationHasChanged = ! viewsInitialized || ( isEmpty != wasEmpty )
Log . e ( " TWIG-t " , " updateEmptyViews called with value: $value initialized: $viewsInitialized isEmpty: $isEmpty wasEmpty: $wasEmpty " )
if ( situationHasChanged ) {
Log . e ( " TWIG-t " , " The situation has changed! toggling views! " )
toggleViews ( isEmpty )
2018-12-30 17:56:50 -08:00
}
}
2018-11-12 10:38:37 -08:00
/ * *
2018-12-02 20:45:59 -08:00
* Defines the basic properties of each FAB button for use while initializing the FAB
2018-11-12 10:38:37 -08:00
* /
2018-12-02 20:45:59 -08:00
enum class HomeFab (
@IdRes val id : Int ,
@DrawableRes val icon : Int ,
@ColorRes val bgColor : Int ,
@StringRes val label : Int ,
@IdRes val destination : Int
) {
/* ordered by when they need to be added to the speed dial (i.e. reverse display order) */
REQUEST (
R . id . fab _request ,
R . drawable . ic _receipt _24dp ,
R . color . icon _request ,
R . string . destination _menu _label _request ,
R . id . nav _request _fragment
) ,
RECEIVE (
R . id . fab _receive ,
R . drawable . ic _qrcode _24dp ,
R . color . icon _receive ,
R . string . destination _menu _label _receive ,
R . id . nav _receive _fragment
) ,
SEND (
R . id . fab _send ,
R . drawable . ic _menu _send ,
R . color . icon _send ,
R . string . destination _menu _label _send ,
R . id . nav _send _fragment
) ;
companion object {
fun fromId ( id : Int ) : HomeFab ? = values ( ) . firstOrNull { it . id == id }
}
2018-11-12 10:38:37 -08:00
}
2018-12-02 20:45:59 -08:00
2018-12-10 08:38:03 -08:00
// ---------------------------------------------------------------------------------------------------------------------
// TODO: Delete these test functions
// ---------------------------------------------------------------------------------------------------------------------
var empty = false
2019-01-31 12:44:25 -08:00
val delay = 50L
2018-12-10 08:38:03 -08:00
lateinit var headerEmptyViews : Array < View >
lateinit var headerFullViews : Array < View >
fun shrink ( ) : Double {
return text _balance _zec . text . toString ( ) . trim ( ) . toDouble ( ) - Random . nextDouble ( 5.0 )
}
fun grow ( ) : Double {
return text _balance _zec . text . toString ( ) . trim ( ) . toDouble ( ) + Random . nextDouble ( 5.0 )
}
fun reduceValue ( ) {
shrink ( ) . let {
if ( it < 0 ) { setZecValue ( 0.0 ) ; toggleViews ( empty ) ; forceRedraw ( ) }
else view ?. postDelayed ( {
setZecValue ( it )
setUsdValue ( it * 75.0 )
reduceValue ( )
} , delay )
}
}
fun increaseValue ( target : Double ) {
grow ( ) . let {
if ( it > target ) { setZecValue ( target ) ; setUsdValue ( target * 75.0 ) ; toggleViews ( empty ) }
else view ?. postDelayed ( {
setZecValue ( it )
setUsdValue ( it * 75.0 )
increaseValue ( target )
if ( headerFullViews [ 0 ] . parent == null || headerEmptyViews [ 0 ] . parent != null ) toggleViews ( false )
forceRedraw ( )
} , delay )
}
}
fun forceRedraw ( ) {
view ?. postDelayed ( {
container _home _header . progress = container _home _header . progress - 0.1f
} , delay * 2 )
}
internal fun toggle ( isEmpty : Boolean ) {
toggleValues ( isEmpty )
}
2019-01-31 12:44:25 -08:00
// TODO: get rid of all of this and consider two different fragments for the header, instead
2018-12-10 08:38:03 -08:00
internal fun toggleViews ( isEmpty : Boolean ) {
2019-01-31 12:44:25 -08:00
Log . e ( " TWIG-t " , " toggling views to isEmpty == $isEmpty " )
var action : ( ) -> Unit
if ( isEmpty ) {
action = {
2018-12-10 08:38:03 -08:00
group _empty _view _items . visibility = View . VISIBLE
group _full _view _items . visibility = View . GONE
headerFullViews . forEach { container _home _header . removeView ( it ) }
headerEmptyViews . forEach {
tryIgnore {
container _home _header . addView ( it )
}
}
2019-01-31 12:44:25 -08:00
}
2018-12-10 08:38:03 -08:00
} else {
2019-01-31 12:44:25 -08:00
action = {
2018-12-10 08:38:03 -08:00
group _empty _view _items . visibility = View . GONE
group _full _view _items . visibility = View . VISIBLE
headerEmptyViews . forEach { container _home _header . removeView ( it ) }
headerFullViews . forEach {
tryIgnore {
container _home _header . addView ( it )
}
}
2019-01-31 12:44:25 -08:00
}
2018-12-10 08:38:03 -08:00
}
2019-01-31 12:44:25 -08:00
view ?. postDelayed ( {
action ( )
viewsInitialized = true
} , delay )
// TODO: the motion layout does not begin in the right state for some reason. Debug this later.
view ?. postDelayed ( :: forceRedraw , delay * 2 )
2018-12-10 08:38:03 -08:00
}
internal fun toggleValues ( isEmpty : Boolean ) {
empty = isEmpty
if ( empty ) {
reduceValue ( )
} else {
increaseValue ( Random . nextDouble ( 20.0 , 100.0 ) )
}
}
2018-11-12 10:38:37 -08:00
}
2018-12-04 23:26:03 -08:00
@Module
abstract class HomeFragmentModule {
@ContributesAndroidInjector
abstract fun contributeHomeFragment ( ) : HomeFragment
2018-12-07 16:27:57 -08:00
}