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
2018-12-07 16:27:57 -08:00
import android.text.format.DateUtils
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
2018-11-12 10:38:37 -08:00
import cash.z.android.wallet.R
2018-12-30 17:37:43 -08:00
import cash.z.android.wallet.data.SampleTransactionRepository
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
import cash.z.android.wallet.vo.WalletTransactionStatus
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 java.math.BigDecimal
import kotlin.random.Random
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
* /
2018-12-04 23:26:03 -08:00
class HomeFragment : BaseFragment ( ) {
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
2018-12-30 17:37:43 -08:00
override fun onCreate ( savedInstanceState : Bundle ? ) {
super . onCreate ( savedInstanceState )
homePresenter = HomePresenter ( this , SampleTransactionRepository ( scope ) )
}
2018-11-12 10:38:37 -08:00
override fun onCreateView (
inflater : LayoutInflater , container : ViewGroup ? ,
savedInstanceState : Bundle ?
) : View ? {
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 )
}
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 )
2018-11-13 21:16:53 -08:00
}
2018-12-07 16:27:57 -08:00
override fun onResume ( ) {
super . onResume ( )
2018-12-30 17:56:50 -08:00
toggleViews ( true )
// view!!.postDelayed( {toggle(false)}, delay * 2L)
2018-12-30 17:37:43 -08:00
// specifying the context/scope is redundant here because launch will automatically use the parent scope but let's do it anyway here to explicitly show that our basefragment is a coroutine scope
scope . launch {
homePresenter . start ( )
}
}
override fun onPause ( ) {
super . onPause ( )
scope . launch {
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 )
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
fun updateBalance ( zecValue : Double , oldZecValue : Double ) {
updateEmptyViews ( zecValue )
// TODO: animate the change in value
setZecValue ( zecValue )
setUsdValue ( 75 * zecValue )
}
fun addTransaction ( transaction : WalletTransaction ) {
transactionAdapter . add ( transaction )
2018-12-30 19:02:56 -08:00
recycler _transactions . smoothScrollToPosition ( 0 )
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
}
2018-12-30 17:56:50 -08:00
private fun updateEmptyViews ( value : Double ) {
val wasEmpty = group _empty _view _items . visibility == View . VISIBLE
// only toggle the views when the situation has changed
if ( wasEmpty && value > 0.0 ) {
toggleViews ( false )
} else if ( ! wasEmpty && value <= 0.0 ) {
toggleViews ( true )
}
}
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
val delay = 20L
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 )
}
internal fun toggleViews ( isEmpty : Boolean ) {
if ( isEmpty ) {
view ?. postDelayed ( {
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 )
}
}
} , delay )
} else {
view ?. postDelayed ( {
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 )
}
}
} , delay )
}
}
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
}