TX fee: in send controller and send confirmation controller doesn't match.

tried sending 0.1 ETH in dev build no.467. The send controller showed fee as $0.01 ut the send confirmation controller showed it as 0$.

I suspect it might be due to rounding. Note that we shouldn't be rounding the values in send AND send confirmation controllers

diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/SendModule.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/SendModule.kt
index 054277d80..a7badc872 100644
--- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/SendModule.kt
+++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/SendModule.kt
@@ -114,7 +114,7 @@ object SendModule {
                 ValueFormatter.format(this.coinValue)
             }
             is SendModule.AmountInfo.CurrencyValueInfo -> {
-                ValueFormatter.formatSimple(this.currencyValue)
+                ValueFormatter.format(this.currencyValue)
             }
         }
     }
diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/viewHelpers/ValueFormatter.kt b/app/src/main/java/io/horizontalsystems/bankwallet/viewHelpers/ValueFormatter.kt
index 3e3d4af4c..e4bd7b535 100644
--- a/app/src/main/java/io/horizontalsystems/bankwallet/viewHelpers/ValueFormatter.kt
+++ b/app/src/main/java/io/horizontalsystems/bankwallet/viewHelpers/ValueFormatter.kt
@@ -105,9 +105,4 @@ object ValueFormatter {
         return spannable
     }

-    fun formatSimple(currencyValue: CurrencyValue): String? {
-        val result = currencyValue.value.setScale(2, RoundingMode.HALF_EVEN)
-        val formatted = currencyFormatter.format(result)
-        return "${currencyValue.currency.symbol}$formatted"
-    }
 }

TX fee: in send controller and send confirmation controller doesn't match.
tried sending 0.1 ETH in dev build no.467. The send controller showed fee as $0.01 ut the send confirmation controller showed it as 0$.

I suspect it might be due to rounding. Note that we shouldn't be rounding the values in send AND send confirmation controllers

package io.horizontalsystems.bankwallet

import android.annotation.TargetApi
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.Configuration
import android.content.res.Resources
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.security.keystore.KeyPermanentlyInvalidatedException
import android.security.keystore.UserNotAuthenticatedException
import android.support.v7.app.AppCompatActivity
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.core.security.EncryptionManager
import io.horizontalsystems.bankwallet.lib.AlertDialogFragment
import java.util.*

abstract class BaseActivity : AppCompatActivity() {

    private var failureRunnable: Runnable? = null
    private var actionRunnable: Runnable? = null
    private var successRunnable: Runnable? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val lightMode = App.localStorage.isLightModeOn
        setTheme(if (lightMode) R.style.LightModeAppTheme else R.style.DarkModeAppTheme)

        window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (requestCode == AUTHENTICATE_FOR_ENCRYPTION) {
            if (resultCode == Activity.RESULT_OK) {
                try {
                    actionRunnable?.run()
                    successRunnable?.run()
                } catch (e: Exception) {
                    failureRunnable?.run()
                }
            } else {
                failureRunnable?.run()
            }
            failureRunnable = null
            actionRunnable = null
            successRunnable = null
        } else {
            super.onActivityResult(requestCode, resultCode, data)
        }
    }

    fun safeExecuteWithKeystore(action: Runnable, onSuccess: Runnable? = null, onFailure: Runnable? = null) {
        try {
            action.run()
            onSuccess?.run()
        } catch (e: UserNotAuthenticatedException) {
            actionRunnable = action
            successRunnable = onSuccess
            failureRunnable = onFailure
            EncryptionManager.showAuthenticationScreen(this, AUTHENTICATE_FOR_ENCRYPTION)
        } catch (e: KeyPermanentlyInvalidatedException) {
            EncryptionManager.showKeysInvalidatedAlert(this)
        } catch (e: Exception) {
            onFailure?.run()
        }

    }

    override fun attachBaseContext(newBase: Context?) {
        newBase?.let {
            super.attachBaseContext(updateBaseContextLocale(it))
        } ?: super.attachBaseContext(newBase)
    }

    protected fun hideSoftKeyboard() {
        getSystemService(InputMethodManager::class.java)?.hideSoftInputFromWindow(currentFocus?.windowToken, 0)
    }

    fun showCustomKeyboardAlert() {
        AlertDialogFragment.newInstance(R.string.Alert_TitleWarning, R.string.Alert_CustomKeyboardIsUsed, R.string.Alert_Ok,
                object : AlertDialogFragment.Listener {
                    override fun onButtonClick() {
                        val imeManager = App.instance.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
                        imeManager.showInputMethodPicker()

                        Handler().postDelayed({
                            onBackPressed()
                        }, (1 * 750).toLong())
                    }
                }).show(supportFragmentManager, "custom_keyboard_alert")
    }

    private fun updateBaseContextLocale(context: Context): Context {
        val language = App.languageManager.currentLanguage
        Locale.setDefault(language)

        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            updateResourcesLocale(context, language)
        } else updateResourcesLocaleLegacy(context, language)
    }

    @TargetApi(Build.VERSION_CODES.N)
    private fun updateResourcesLocale(context: Context, locale: Locale): Context {
        val configuration: Configuration = context.resources.configuration
        configuration.setLocale(locale)
        return context.createConfigurationContext(configuration)
    }

    @SuppressWarnings("deprecation")
    private fun updateResourcesLocaleLegacy(context: Context, locale: Locale): Context {
        val resources: Resources = context.resources
        val configuration: Configuration = resources.configuration
        configuration.locale = locale
        resources.updateConfiguration(configuration, resources.displayMetrics)
        return context
    }

    companion object {
        const val AUTHENTICATE_FOR_ENCRYPTION = 23
    }

}

package io.horizontalsystems.bankwallet

import android.os.Bundle
import android.security.keystore.KeyPermanentlyInvalidatedException
import android.support.v7.app.AppCompatActivity
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.core.security.EncryptionManager
import io.horizontalsystems.bankwallet.modules.backup.BackupModule
import io.horizontalsystems.bankwallet.modules.backup.BackupPresenter
import io.horizontalsystems.bankwallet.modules.guest.GuestModule
import io.horizontalsystems.bankwallet.modules.main.MainModule
import io.horizontalsystems.bankwallet.modules.pin.PinModule
import java.security.UnrecoverableKeyException

class LauncherActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        try {
            redirectToCorrectPage()
        } catch (e: Exception) {
            when (e) {
                is KeyPermanentlyInvalidatedException,
                is UnrecoverableKeyException -> EncryptionManager.showKeysInvalidatedAlert(this)
            }
        }
    }

    private fun redirectToCorrectPage() {
        if (!EncryptionManager.isDeviceLockEnabled(this)) {
            EncryptionManager.showNoDeviceLockWarning(this)
            return
        } else if (!App.authManager.isLoggedIn) {
            GuestModule.start(this)
        } else if (!App.localStorage.iUnderstand) {
            BackupModule.start(this, BackupPresenter.DismissMode.SET_PIN)
        } else if(App.secureStorage.pinIsEmpty()) {
            PinModule.startForSetPin(this)
        } else {
            MainModule.start(this)
        }
        finish()
    }

}

package io.horizontalsystems.bankwallet

import android.arch.lifecycle.LifecycleOwner
import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.Observer
import java.util.concurrent.atomic.AtomicBoolean

class SingleLiveEvent<T> : MutableLiveData<T>() {

    private val mPending = AtomicBoolean(false)

    override fun observe(owner: LifecycleOwner, observer: Observer<T>) {
        super.observe(owner, Observer<T> { t ->
            if (mPending.compareAndSet(true, false)) {
                observer.onChanged(t)
            }
        })
    }

    override fun setValue(t: T?) {
        mPending.set(true)
        super.setValue(t)
    }

    override fun postValue(value: T?) {
        mPending.set(true)
        super.postValue(value)
    }

    fun call() {
        value = null
    }

}

package io.horizontalsystems.bankwallet.core

import android.app.Application
import android.content.SharedPreferences
import android.preference.PreferenceManager
import com.squareup.leakcanary.LeakCanary
import io.horizontalsystems.bankwallet.core.factories.AdapterFactory
import io.horizontalsystems.bankwallet.core.factories.WalletFactory
import io.horizontalsystems.bankwallet.core.managers.*
import io.horizontalsystems.bankwallet.core.security.EncryptionManager
import io.horizontalsystems.bankwallet.core.storage.AppDatabase
import io.horizontalsystems.bankwallet.core.storage.RatesRepository
import io.horizontalsystems.bankwallet.core.storage.StorableCoinsRepository
import io.horizontalsystems.bankwallet.modules.fulltransactioninfo.FullTransactionInfoFactory
import io.horizontalsystems.bitcoinkit.BitcoinKit
import io.horizontalsystems.ethereumkit.EthereumKit
import java.util.*

class App : Application() {

    companion object {

        lateinit var preferences: SharedPreferences

        lateinit var secureStorage: ISecuredStorage
        lateinit var localStorage: ILocalStorage
        lateinit var encryptionManager: EncryptionManager
        lateinit var wordsManager: WordsManager
        lateinit var authManager: AuthManager
        lateinit var randomManager: IRandomProvider
        lateinit var networkManager: INetworkManager
        lateinit var currencyManager: ICurrencyManager
        lateinit var backgroundManager: BackgroundManager
        lateinit var languageManager: ILanguageManager
        lateinit var systemInfoManager: ISystemInfoManager
        lateinit var pinManager: IPinManager
        lateinit var lockManager: ILockManager
        lateinit var appConfigProvider: IAppConfigProvider
        lateinit var walletManager: IWalletManager
        lateinit var coinManager: CoinManager

        lateinit var rateSyncer: RateSyncer
        lateinit var rateManager: RateManager
        lateinit var networkAvailabilityManager: NetworkAvailabilityManager
        lateinit var appDatabase: AppDatabase
        lateinit var rateStorage: IRateStorage
        lateinit var coinsStorage: ICoinStorage
        lateinit var transactionInfoFactory: FullTransactionInfoFactory
        lateinit var transactionDataProviderManager: TransactionDataProviderManager
        lateinit var appCloseManager: AppCloseManager

        val testMode = true

        lateinit var instance: App
            private set

        var lastExitDate: Long = 0
    }

    override fun onCreate() {
        super.onCreate()

        if (LeakCanary.isInAnalyzerProcess(this)) {
            // This process is dedicated to LeakCanary for heap analysis.
            // You should not init your app in this process.
            return
        }
        LeakCanary.install(this)

        // Initialize BitcoinKit
        BitcoinKit.init(this)

        // Initialize EthereumKit
        EthereumKit.init(this)

        instance = this
        preferences = PreferenceManager.getDefaultSharedPreferences(applicationContext)

        val fallbackLanguage = Locale("en")

        appConfigProvider = AppConfigProvider()
        backgroundManager = BackgroundManager(this)
        encryptionManager = EncryptionManager()
        secureStorage = SecuredStorageManager(encryptionManager)

        appDatabase = AppDatabase.getInstance(this)
        rateStorage = RatesRepository(appDatabase)
        coinsStorage = StorableCoinsRepository(appDatabase)
        localStorage = LocalStorageManager()

        networkManager = NetworkManager()
        rateManager = RateManager(rateStorage, networkManager)
        coinManager = CoinManager(appConfigProvider, coinsStorage)
        authManager = AuthManager(secureStorage, localStorage, coinManager, rateManager)

        wordsManager = WordsManager(localStorage)
        randomManager = RandomProvider()
        systemInfoManager = SystemInfoManager()
        pinManager = PinManager(secureStorage)
        lockManager = LockManager(secureStorage, authManager)
        languageManager = LanguageManager(localStorage, appConfigProvider, fallbackLanguage)
        currencyManager = CurrencyManager(localStorage, appConfigProvider)

        networkAvailabilityManager = NetworkAvailabilityManager()

        walletManager = WalletManager(coinManager, authManager, WalletFactory(AdapterFactory(appConfigProvider)))
        rateSyncer = RateSyncer(rateManager, walletManager, currencyManager, networkAvailabilityManager)

        appCloseManager = AppCloseManager()

        transactionDataProviderManager = TransactionDataProviderManager(appConfigProvider, localStorage)
        transactionInfoFactory = FullTransactionInfoFactory(networkManager, transactionDataProviderManager)

        authManager.walletManager = walletManager
        authManager.pinManager = pinManager
    }

}

package io.horizontalsystems.bankwallet.core

import io.horizontalsystems.bankwallet.entities.PaymentRequestAddress
import io.horizontalsystems.bankwallet.entities.TransactionAddress
import io.horizontalsystems.bankwallet.entities.TransactionRecord
import io.horizontalsystems.bitcoinkit.BitcoinKit
import io.horizontalsystems.bitcoinkit.managers.UnspentOutputSelector
import io.horizontalsystems.bitcoinkit.models.BlockInfo
import io.horizontalsystems.bitcoinkit.models.TransactionInfo
import io.reactivex.BackpressureStrategy
import io.reactivex.Flowable
import io.reactivex.Single
import io.reactivex.subjects.BehaviorSubject
import io.reactivex.subjects.PublishSubject
import java.math.BigDecimal
import java.math.RoundingMode

class BitcoinAdapter(val words: List<String>, network: BitcoinKit.NetworkType, newWallet: Boolean, walletId: String?) : IAdapter, BitcoinKit.Listener {

    private var bitcoinKit = BitcoinKit(words, network, newWallet = newWallet, walletId = walletId)
    private val satoshisInBitcoin = Math.pow(10.0, 8.0).toBigDecimal()

    private val progressSubject: BehaviorSubject<Double> = BehaviorSubject.createDefault(0.0)

    private val balanceSubject: BehaviorSubject<BigDecimal> = BehaviorSubject.createDefault(balance)
    private val stateSubject: BehaviorSubject<AdapterState> = BehaviorSubject.createDefault(AdapterState.Syncing(progressSubject))

    override val balance: BigDecimal
        get() = bitcoinKit.balance.toBigDecimal().divide(satoshisInBitcoin, 8, RoundingMode.HALF_EVEN)

    override val balanceObservable: Flowable<BigDecimal> = balanceSubject.toFlowable(BackpressureStrategy.DROP)
    override val stateObservable: Flowable<AdapterState> = stateSubject.toFlowable(BackpressureStrategy.DROP)

    override val confirmationsThreshold: Int = 6
    override val lastBlockHeight: Int get() = bitcoinKit.lastBlockHeight
    override val lastBlockHeightUpdatedSignal: PublishSubject<Unit> = PublishSubject.create()

    override val transactionRecordsSubject: PublishSubject<List<TransactionRecord>> = PublishSubject.create()

    override val debugInfo: String = ""

    override val receiveAddress: String get() = bitcoinKit.receiveAddress()

    override fun start() {
        bitcoinKit.listener = this
        bitcoinKit.start()
    }

    override fun refresh() {
        bitcoinKit.refresh()
    }

    override fun clear() {
        bitcoinKit.clear()
    }

    override fun parsePaymentAddress(address: String): PaymentRequestAddress {
        val paymentData = bitcoinKit.parsePaymentAddress(address)
        return PaymentRequestAddress(paymentData.address, paymentData.amount?.toBigDecimal())
    }

    override fun send(address: String, value: BigDecimal, completion: ((Throwable?) -> (Unit))?) {
        try {
            bitcoinKit.send(address, (value * satoshisInBitcoin).toInt())
            completion?.invoke(null)
        } catch (ex: Exception) {
            completion?.invoke(ex)
        }
    }

    override fun fee(value: BigDecimal, address: String?, senderPay: Boolean): BigDecimal {
        try {
            val amount = (value * satoshisInBitcoin).toInt()
            val fee = bitcoinKit.fee(amount, address, senderPay)
            return fee.toBigDecimal().divide(satoshisInBitcoin, 8, RoundingMode.HALF_EVEN)
        } catch (e: UnspentOutputSelector.Error.InsufficientUnspentOutputs) {
            val fee = e.fee.toBigDecimal().divide(satoshisInBitcoin, 8, RoundingMode.HALF_EVEN)
            throw Error.InsufficientAmount(fee)
        } catch (e: UnspentOutputSelector.Error.EmptyUnspentOutputs) {
            throw Error.InsufficientAmount(BigDecimal.ZERO)
        }
    }

    override fun validate(address: String) {
        bitcoinKit.validateAddress(address)
    }

    override fun getTransactionsObservable(hashFrom: String?, limit: Int): Single<List<TransactionRecord>> {
        return bitcoinKit.transactions(hashFrom, limit).map { it.map { transactionRecord(it) } }
    }

    //
    // BitcoinKit Listener implementations
    //
    override fun onBalanceUpdate(bitcoinKit: BitcoinKit, balance: Long) {
        balanceSubject.onNext(balance.toBigDecimal().divide(satoshisInBitcoin, 8, RoundingMode.HALF_EVEN))
    }

    override fun onLastBlockInfoUpdate(bitcoinKit: BitcoinKit, blockInfo: BlockInfo) {
        lastBlockHeightUpdatedSignal.onNext(Unit)
    }

    override fun onKitStateUpdate(bitcoinKit: BitcoinKit, state: BitcoinKit.KitState) {
        when (state) {
            is BitcoinKit.KitState.Synced -> {
                if (stateSubject.value !is AdapterState.Synced) {
                    stateSubject.onNext(AdapterState.Synced)
                }
            }
            is BitcoinKit.KitState.NotSynced -> {
                stateSubject.onNext(AdapterState.NotSynced)
            }
            is BitcoinKit.KitState.Syncing -> {
                progressSubject.onNext(state.progress)

                if (stateSubject.value !is AdapterState.Syncing) {
                    stateSubject.onNext(AdapterState.Syncing(progressSubject))
                }
            }
        }
    }

    override fun onTransactionsUpdate(bitcoinKit: BitcoinKit, inserted: List<TransactionInfo>, updated: List<TransactionInfo>, deleted: List<Int>) {
        val records = mutableListOf<TransactionRecord>()

        for (info in inserted) {
            records.add(transactionRecord(info))
        }
        for (info in updated) {
            records.add(transactionRecord(info))
        }

        transactionRecordsSubject.onNext(records)

    }

    private fun transactionRecord(transaction: TransactionInfo) =
            TransactionRecord(
                    transaction.transactionHash,
                    transaction.blockHeight?.toLong() ?: 0,
                    transaction.amount.toBigDecimal().divide(satoshisInBitcoin, 8, RoundingMode.HALF_EVEN),
                    transaction.timestamp,
                    transaction.from.map {
                        val address = TransactionAddress()
                        address.address = it.address
                        address.mine = it.mine
                        address
                    },
                    transaction.to.map {
                        val address = TransactionAddress()
                        address.address = it.address
                        address.mine = it.mine
                        address
                    }
            )

    companion object {

        fun createBitcoin(words: List<String>, testMode: Boolean, newWallet: Boolean, walletId: String?): BitcoinAdapter {
            val network = if (testMode) BitcoinKit.NetworkType.TestNet else BitcoinKit.NetworkType.MainNet
            return BitcoinAdapter(words, network, newWallet, walletId)
        }

        fun createBitcoinCash(words: List<String>, testMode: Boolean, newWallet: Boolean, walletId: String?): BitcoinAdapter {
            val network = if (testMode) BitcoinKit.NetworkType.TestNetBitCash else BitcoinKit.NetworkType.MainNetBitCash
            return BitcoinAdapter(words, network, newWallet, walletId)
        }
    }

}

package io.horizontalsystems.bankwallet.core

import io.horizontalsystems.bankwallet.entities.PaymentRequestAddress
import io.horizontalsystems.bankwallet.entities.TransactionAddress
import io.horizontalsystems.bankwallet.entities.TransactionRecord
import io.horizontalsystems.ethereumkit.EthereumKit
import io.horizontalsystems.ethereumkit.EthereumKit.NetworkType
import io.horizontalsystems.ethereumkit.models.Transaction
import io.reactivex.BackpressureStrategy
import io.reactivex.Flowable
import io.reactivex.Single
import io.reactivex.subjects.BehaviorSubject
import io.reactivex.subjects.PublishSubject
import java.math.BigDecimal
import java.math.RoundingMode

class EthereumAdapter(words: List<String>, network: NetworkType) : IAdapter, EthereumKit.Listener {

    private var ethereumKit = EthereumKit(words, network)
    private val weisInEther = Math.pow(10.0, 18.0).toBigDecimal()

    private val progressSubject: BehaviorSubject<Double> = BehaviorSubject.createDefault(1.0)

    private val balanceSubject: BehaviorSubject<BigDecimal> = BehaviorSubject.createDefault(balance)
    private val stateSubject: BehaviorSubject<AdapterState> = BehaviorSubject.createDefault(AdapterState.Syncing(progressSubject))

    override val balance: BigDecimal
        get() = ethereumKit.balance.toBigDecimal()

    override val balanceObservable: Flowable<BigDecimal> = balanceSubject.toFlowable(BackpressureStrategy.DROP)
    override val stateObservable: Flowable<AdapterState> = stateSubject.toFlowable(BackpressureStrategy.DROP)

    override val confirmationsThreshold: Int = 12
    override val lastBlockHeight: Int? get() = ethereumKit.lastBlockHeight
    override val lastBlockHeightUpdatedSignal: PublishSubject<Unit> = PublishSubject.create()

    override val transactionRecordsSubject: PublishSubject<List<TransactionRecord>> = PublishSubject.create()

    override val debugInfo: String = ""

    override val receiveAddress: String get() = ethereumKit.receiveAddress()

    override fun start() {
        ethereumKit.listener = this
        ethereumKit.start()
    }

    override fun refresh() {
        ethereumKit.refresh()
    }

    override fun clear() {
        ethereumKit.clear()
    }

    override fun parsePaymentAddress(address: String): PaymentRequestAddress {
        return PaymentRequestAddress(address)
    }

    override fun send(address: String, value: BigDecimal, completion: ((Throwable?) -> (Unit))?) {
        ethereumKit.send(address, value.toDouble(), completion)
    }

    override fun fee(value: BigDecimal, address: String?, senderPay: Boolean): BigDecimal {
        val fee = ethereumKit.fee().toBigDecimal()
        if (senderPay && balance.minus(value).minus(fee) < BigDecimal.ZERO) {
            throw Error.InsufficientAmount(fee)
        }
        return fee
    }

    override fun validate(address: String) {
        ethereumKit.validateAddress(address)
    }

    override fun balanceUpdated(balance: Double) {
        balanceSubject.onNext(balance.toBigDecimal())
    }

    override fun lastBlockHeightUpdated(height: Int) {
        lastBlockHeightUpdatedSignal.onNext(Unit)
    }

    override fun onKitStateUpdate(state: EthereumKit.KitState) {
        when (state) {
            is EthereumKit.KitState.Synced -> {
                stateSubject.onNext(AdapterState.Synced)
            }
            is EthereumKit.KitState.NotSynced -> {
                stateSubject.onNext(AdapterState.NotSynced)
            }
            is EthereumKit.KitState.Syncing -> {
                progressSubject.onNext(state.progress)

                if (stateSubject.value !is AdapterState.Syncing) {
                    stateSubject.onNext(AdapterState.Syncing(progressSubject))
                }
            }
        }
    }

    override fun transactionsUpdated(inserted: List<Transaction>, updated: List<Transaction>, deleted: List<Int>) {
        val records = mutableListOf<TransactionRecord>()

        for (info in inserted) {
            records.add(transactionRecord(info))
        }

        for (info in updated) {
            records.add(transactionRecord(info))
        }

        transactionRecordsSubject.onNext(records)
    }

    override fun getTransactionsObservable(hashFrom: String?, limit: Int): Single<List<TransactionRecord>> {
        return ethereumKit.transactions(hashFrom, limit).map { it.map { transactionRecord(it) } }
    }

    private fun transactionRecord(transaction: Transaction): TransactionRecord {
        val amountEther: BigDecimal = weisToEther(transaction.value) ?: BigDecimal.ZERO

        val mineAddress = ethereumKit.receiveAddress().toLowerCase()

        val from = TransactionAddress()
        from.address = transaction.from
        from.mine = transaction.from.toLowerCase() == mineAddress

        val to = TransactionAddress()
        to.address = transaction.to
        to.mine = transaction.to.toLowerCase() == mineAddress

        return TransactionRecord(
                transaction.hash,
                transaction.blockNumber,
                if (from.mine) amountEther.unaryMinus() else amountEther,
                transaction.timeStamp,
                listOf(from),
                listOf(to)
        )
    }

    private fun weisToEther(amount: String): BigDecimal? = try {
        BigDecimal(amount).divide(weisInEther, 18, RoundingMode.HALF_EVEN)
    } catch (ex: Exception) {
        null
    }

//    private fun calculateFee(gasUsed: String, gasPrice: String): Double {
//        val feeInWeis = BigDecimal(gasUsed).multiply(BigDecimal(gasPrice))
//        return feeInWeis.divide(weisInEther.toBigDecimal()).toDouble()
//    }
//

    companion object {

        fun createEthereum(words: List<String>, testMode: Boolean): EthereumAdapter {
            val network = if (testMode) EthereumKit.NetworkType.Kovan else EthereumKit.NetworkType.MainNet
            return EthereumAdapter(words, network)
        }
    }

}

package io.horizontalsystems.bankwallet.core

import android.view.View
import io.horizontalsystems.bankwallet.ui.view.SingleClickListener
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable

fun <T> io.reactivex.Flowable<T>.subscribeAsync(disposables: CompositeDisposable, onNext: ((T) -> Unit)? = null, onError: ((Throwable) -> Unit)? = null, onComplete: (() -> Unit)? = null, onFinished: (() -> Unit)? = null): Disposable {
    val disposable = this
            .subscribeOn(io.reactivex.schedulers.Schedulers.io())
            .unsubscribeOn(io.reactivex.schedulers.Schedulers.io())
            .observeOn(io.reactivex.android.schedulers.AndroidSchedulers.mainThread())
            .subscribeWith(object : io.reactivex.subscribers.DisposableSubscriber<T>() {
                override fun onNext(t: T) {
                    onNext?.invoke(t)
                }

                override fun onComplete() {
                    onComplete?.invoke()
                    onFinished?.invoke()
                }

                override fun onError(throwable: Throwable) {
                    onError?.invoke(throwable)
                    onFinished?.invoke()
                }
            })
    disposables.add(disposable)
    return disposable
}

fun <T> io.reactivex.subjects.PublishSubject<T>.subscribeAsync(disposables: CompositeDisposable, onNext: ((T) -> Unit)): Disposable {
    val disposable = this
            .subscribeOn(io.reactivex.schedulers.Schedulers.io())
            .unsubscribeOn(io.reactivex.schedulers.Schedulers.io())
            .observeOn(io.reactivex.android.schedulers.AndroidSchedulers.mainThread())
            .subscribe(onNext)

    disposables.add(disposable)
    return disposable
}

fun io.reactivex.Completable.subscribeAsync(disposables: CompositeDisposable, onComplete: (() -> Unit), onError: ((Throwable) -> Unit)): Disposable {
    val disposable = this
            .subscribeOn(io.reactivex.schedulers.Schedulers.io())
            .unsubscribeOn(io.reactivex.schedulers.Schedulers.io())
            .observeOn(io.reactivex.android.schedulers.AndroidSchedulers.mainThread())
            .subscribe(onComplete, onError)

    disposables.add(disposable)
    return disposable
}

//View

fun View.setOnSingleClickListener(l: ((v: View) -> Unit)) {
    this.setOnClickListener(
            object : SingleClickListener() {
                override fun onSingleClick(v: View) {
                    l.invoke(v)
                }
            })
}

package io.horizontalsystems.bankwallet.core

import android.support.v4.hardware.fingerprint.FingerprintManagerCompat
import com.google.gson.JsonObject
import io.horizontalsystems.bankwallet.entities.*
import io.horizontalsystems.bankwallet.entities.Currency
import io.horizontalsystems.bankwallet.modules.fulltransactioninfo.FullTransactionInfoModule
import io.horizontalsystems.bankwallet.modules.send.SendModule
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode
import io.reactivex.Flowable
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.subjects.BehaviorSubject
import io.reactivex.subjects.PublishSubject
import java.math.BigDecimal
import java.util.*

interface IWalletManager {
    val wallets: List<Wallet>
    val walletsUpdatedSignal: Observable<Unit>

    fun refreshWallets()
    fun initWallets()
    fun clear()
}

interface ILocalStorage {
    var currentLanguage: String?
    var isBackedUp: Boolean
    var isBiometricOn: Boolean
    var sendInputType: SendModule.InputType?
    var isLightModeOn: Boolean
    var iUnderstand: Boolean
    var baseCurrencyCode: String?
    var blockTillDate: Long?
    var isNewWallet: Boolean
    var failedAttempts: Int?
    var lockoutUptime: Long?
    var baseBitcoinProvider: String?
    var baseEthereumProvider: String?

    fun clear()
}

interface ISecuredStorage {
    val authData: AuthData?
    fun saveAuthData(authData: AuthData)
    fun noAuthData(): Boolean
    val savedPin: String?
    fun savePin(pin: String)
    fun pinIsEmpty(): Boolean
}

interface IRandomProvider {
    fun getRandomIndexes(count: Int): List<Int>
}

interface INetworkManager {
    fun getLatestRate(coin: String, currency: String): Flowable<LatestRate>
    fun getRate(coinCode: String, currency: String, timestamp: Long): Flowable<BigDecimal>
    fun getTransaction(host: String, path: String): Flowable<JsonObject>
    fun ping(host: String, url: String): Flowable<JsonObject>
}

interface IEncryptionManager {
    fun encrypt(data: String): String
    fun decrypt(data: String): String
    fun getCryptoObject(): FingerprintManagerCompat.CryptoObject?
}

interface IClipboardManager {
    fun copyText(text: String)
    fun getCopiedText(): String
    val hasPrimaryClip: Boolean
}

interface ICurrencyManager {
    val baseCurrency: Currency
    val baseCurrencyUpdatedSignal: Observable<Unit>
    val currencies: List<Currency>
    fun setBaseCurrency(code: String)
}

interface ITransactionDataProviderManager {
    val baseProviderUpdatedSignal: Observable<Unit>

    fun providers(coinCode: CoinCode): List<FullTransactionInfoModule.Provider>
    fun baseProvider(coinCode: CoinCode): FullTransactionInfoModule.Provider
    fun setBaseProvider(name: String, coinCode: CoinCode)

    fun bitcoin(name: String): FullTransactionInfoModule.BitcoinForksProvider
    fun bitcoinCash(name: String): FullTransactionInfoModule.BitcoinForksProvider
    fun ethereum(name: String): FullTransactionInfoModule.EthereumForksProvider
}

interface IKeyStoreSafeExecute {
    fun safeExecute(action: Runnable, onSuccess: Runnable? = null, onFailure: Runnable? = null)
}

interface IWordsManager {
    var isBackedUp: Boolean
    var backedUpSignal: PublishSubject<Unit>

    fun validate(words: List<String>)
    fun generateWords(): List<String>
}

interface ILanguageManager {
    var currentLanguage: Locale
    var preferredLanguage: Locale?
    val availableLanguages: List<Locale>
}

sealed class AdapterState {
    object Synced : AdapterState()
    class Syncing(val progressSubject: BehaviorSubject<Double>) : AdapterState()
    object NotSynced : AdapterState()
}

interface IAdapter {
    val balance: BigDecimal

    val balanceObservable: Flowable<BigDecimal>
    val stateObservable: Flowable<AdapterState>

    val confirmationsThreshold: Int
    val lastBlockHeight: Int?
    val lastBlockHeightUpdatedSignal: PublishSubject<Unit>

    val transactionRecordsSubject: PublishSubject<List<TransactionRecord>>

    val debugInfo: String

    fun start()
    fun refresh()
    fun clear()

    fun parsePaymentAddress(address: String): PaymentRequestAddress

    fun send(address: String, value: BigDecimal, completion: ((Throwable?) -> (Unit))? = null)

    @Throws(Error.InsufficientAmount::class)
    fun fee(value: BigDecimal, address: String?, senderPay: Boolean): BigDecimal

    @Throws
    fun validate(address: String)

    val receiveAddress: String
    fun getTransactionsObservable(hashFrom: String?, limit: Int): Single<List<TransactionRecord>>
}

interface ISystemInfoManager {
    var appVersion: String
    var biometryType: BiometryType
    fun phoneHasFingerprintSensor(): Boolean
    fun touchSensorCanBeUsed(): Boolean
}

interface IPinManager {
    fun safeLoad()
    var pin: String?
    val isPinSet: Boolean
    fun store(pin: String)
    fun validate(pin: String): Boolean
    fun clear()
}

interface ILockManager {
    var isLocked: Boolean
    fun lock()
    fun onUnlock()
    fun didEnterBackground()
    fun willEnterForeground()
}

interface IAppConfigProvider {
    val testMode: Boolean
    val localizations: List<String>
    val currencies: List<Currency>
    val defaultCoins: List<Coin>
}

interface IOneTimerDelegate {
    fun onFire()
}

interface IRateStorage {
    fun latestRateObservable(coinCode: CoinCode, currencyCode: String): Flowable<Rate>
    fun rateObservable(coinCode: CoinCode, currencyCode: String, timestamp: Long): Flowable<List<Rate>>
    fun save(rate: Rate)
    fun saveLatest(rate: Rate)
    fun deleteAll()
    fun zeroRatesObservable(currencyCode: String): Single<List<Rate>>
}

interface ICoinStorage {
    fun enabledCoinsObservable(): Flowable<List<Coin>>
    fun allCoinsObservable(): Flowable<List<Coin>>
    fun save(coins: List<Coin>)
    fun deleteAll()
}

interface ILockoutManager {
    fun didFailUnlock()
    fun dropFailedAttempts()

    val currentState: LockoutState
}

interface IUptimeProvider {
    val uptime: Long
}

interface ILockoutUntilDateFactory {
    fun lockoutUntilDate(failedAttempts: Int, lockoutTimestamp: Long, uptime: Long): Date?
}

interface ICurrentDateProvider {
    val currentDate: Date
}

interface ICoinManager {
    val coinsUpdatedSignal: PublishSubject<Unit>
    var coins: List<Coin>
    val allCoinsObservable: Flowable<List<Coin>>
    fun enableDefaultCoins()
    fun clear()
}

sealed class Error : Exception() {
    class InsufficientAmount(val fee: BigDecimal) : Error()
    class CoinTypeException : Error()
}

package io.horizontalsystems.bankwallet.core

import android.content.Context
import android.content.pm.ApplicationInfo
import android.provider.Settings
import android.view.inputmethod.InputMethodManager

object Utils {

    fun isUsingCustomKeyboard(context: Context): Boolean {

        val inputMethodManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
        val inputMethodProperties = inputMethodManager.enabledInputMethodList
        for (i in 0 until inputMethodProperties.size) {
            val imi = inputMethodProperties[i]
            if (imi.id == Settings.Secure.getString(context.contentResolver, Settings.Secure.DEFAULT_INPUT_METHOD)) {
                if (imi.serviceInfo.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM == 0) {
                    return true
                }
            }
        }

        return false
    }
}

package io.horizontalsystems.bankwallet.entities

import io.horizontalsystems.hdwalletkit.Mnemonic
import java.util.*

data class AuthData(val words: List<String> = Mnemonic().generate(), val walletId: String = UUID.randomUUID().toString())

package io.horizontalsystems.bankwallet.entities

enum class BiometryType {
    FACE, FINGER, NONE
}

package io.horizontalsystems.bankwallet.entities

import android.arch.persistence.room.Entity
import android.arch.persistence.room.PrimaryKey
import android.arch.persistence.room.TypeConverters
import io.horizontalsystems.bankwallet.core.storage.CoinTypeConverter

sealed class CoinType {
    object BitcoinCash : CoinType()
    object Bitcoin : CoinType()
    object Ethereum : CoinType()
    class Erc20(val address: String, val decimal: Int) : CoinType()
}

data class Coin(
        val title: String,
        val code: String,
        val type: CoinType) {

    override fun equals(other: Any?): Boolean {
        if (other is Coin) {
            return code == other.code
        }
        return super.equals(other)
    }

    override fun hashCode(): Int {
        var result = title.hashCode()
        result = 31 * result + code.hashCode()
        result = 31 * result + type.hashCode()
        return result
    }
}

@Entity
@TypeConverters(CoinTypeConverter::class)
data class StorableCoin(
        @PrimaryKey val coinCode: String,
        var coinTitle: String,
        val coinType: CoinType,
        var enabled: Boolean,
        var order: Int? = null)

package io.horizontalsystems.bankwallet.entities

import io.horizontalsystems.bankwallet.modules.transactions.CoinCode
import java.math.BigDecimal

data class CoinValue(val coinCode: CoinCode, val value: BigDecimal) {

    override fun equals(other: Any?): Boolean {
        if (other is CoinValue) {
            return coinCode == other.coinCode && value == other.value

        }

        return super.equals(other)
    }

    override fun hashCode(): Int {
        var result = coinCode.hashCode()
        result = 31 * result + value.hashCode()
        return result
    }

}

package io.horizontalsystems.bankwallet.entities

class Currency(val code: String, val symbol: String)

package io.horizontalsystems.bankwallet.entities

import java.math.BigDecimal

data class CurrencyValue(val currency: Currency, val value: BigDecimal)

package io.horizontalsystems.bankwallet.entities

data class FullTransactionItem(
        val titleResId: Int? = null,
        val title: String? = null,
        val value: String? = null,
        val clickable: Boolean = false,
        val url: String? = null,
        val icon: FullTransactionIcon? = null,
        val dimmed: Boolean = false
)

data class FullTransactionSection(val items: List<FullTransactionItem>)
data class FullTransactionRecord(
        val providerName: String,
        val sections: List<FullTransactionSection>
)

enum class FullTransactionIcon { HASH, TIME, PERSON, BLOCK, CHECK }

package io.horizontalsystems.bankwallet.entities

import com.google.gson.annotations.SerializedName
import java.math.BigDecimal

data class LatestRate(@SerializedName("rate") val value: BigDecimal, @SerializedName("date") val timestamp: Long)

package io.horizontalsystems.bankwallet.entities

import java.util.*

sealed class LockoutState {
    data class Unlocked(val attemptsLeft: Int?) : LockoutState()
    data class Locked(val until: Date) : LockoutState()
}

package io.horizontalsystems.bankwallet.entities

import java.math.BigDecimal

class PaymentRequestAddress(val address: String, val amount: BigDecimal? = null)

package io.horizontalsystems.bankwallet.entities

import android.arch.persistence.room.Entity
import android.arch.persistence.room.TypeConverters
import io.horizontalsystems.bankwallet.core.storage.DatabaseConverters
import java.math.BigDecimal
import java.util.*

@Entity(primaryKeys = ["coinCode", "currencyCode", "timestamp", "isLatest"])
@TypeConverters(DatabaseConverters::class)
data class Rate(var coinCode: String,
                var currencyCode: String,
                var value: BigDecimal,
                var timestamp: Long,
                var isLatest: Boolean) {

    val expired: Boolean
        get() {
            val diff = (Date().time / 1000) - timestamp
            return diff > 60 * 10
        }
}

package io.horizontalsystems.bankwallet.entities

import io.horizontalsystems.bankwallet.modules.transactions.CoinCode
import java.math.BigDecimal

data class TransactionRecord(var transactionHash: String) {
    var blockHeight: Long = 0L
    var amount: BigDecimal = BigDecimal.ZERO
    var timestamp: Long = 0L
    var from: List<TransactionAddress> = listOf()
    var to: List<TransactionAddress> = listOf()

    constructor(transactionHash: String, blockHeight: Long, amount: BigDecimal, timestamp: Long, from: List<TransactionAddress>, to: List<TransactionAddress>) : this(transactionHash) {
        this.blockHeight = blockHeight
        this.amount = amount
        this.timestamp = timestamp
        this.from = from
        this.to = to
    }

}

data class TransactionItem(val coinCode: CoinCode, val record: TransactionRecord)

class TransactionAddress {
    var address = ""
    var mine = false
}

package io.horizontalsystems.bankwallet.entities

import io.horizontalsystems.bankwallet.core.IAdapter
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode

class Wallet(val title: String, val coinCode: CoinCode, val adapter: IAdapter)

package io.horizontalsystems.bankwallet.lib

import android.app.Dialog
import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.support.v7.app.AlertDialog
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import io.horizontalsystems.bankwallet.R

class AlertDialogFragment : DialogFragment() {

    interface Listener {
        fun onButtonClick()
    }

    private var title: Int? = null
    private var description: Int? = null
    private var buttonText: Int? = null

    private var listener: Listener? = null

    override fun onCreateDialog(bundle: Bundle?): Dialog {

        val builder = activity?.let { AlertDialog.Builder(it, R.style.AlertDialog) }

        val rootView = View.inflate(context, R.layout.fragment_alert_dialog, null) as ViewGroup
        builder?.setView(rootView)

        title?.let { rootView.findViewById<TextView>(R.id.txtTitle)?.setText(it) }
        description?.let { rootView.findViewById<TextView>(R.id.txtDescription)?.setText(it) }
        buttonText?.let { rootView.findViewById<TextView>(R.id.txtButton)?.setText(it) }

        rootView.findViewById<TextView>(R.id.txtButton)?.let { txtButton ->
            buttonText?.let { txtButton.setText(it) }
            txtButton.setOnClickListener {
                listener?.onButtonClick()
                dismiss()
            }
        }

        val mDialog = builder?.create()
        mDialog?.setCanceledOnTouchOutside(false)
        isCancelable = false

        return mDialog as Dialog
    }

    companion object {
        fun newInstance(title: Int, description: Int, buttonText: Int, listener: Listener? = null): AlertDialogFragment {
            val dialog = AlertDialogFragment()
            dialog.title = title
            dialog.description = description
            dialog.buttonText = buttonText
            dialog.listener = listener
            return dialog
        }
    }

}

package io.horizontalsystems.bankwallet.lib

import android.support.v7.widget.RecyclerView
import android.text.Editable
import android.text.TextWatcher
import android.widget.EditText

class EditTextViewHolder(private val editText: EditText, listener: WordsChangedListener) : RecyclerView.ViewHolder(editText) {

    interface WordsChangedListener {
        fun set(position: Int, value: String)
    }

    init {
        editText.addTextChangedListener(object: TextWatcher {
            override fun afterTextChanged(s: Editable) {
                listener.set(adapterPosition, s.toString().trim())
            }

            override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit

            override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
        })
    }

    fun bind(position: Int) {
        editText.hint = "${position + 1}"
    }

}

package io.horizontalsystems.bankwallet.lib

import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.TextView
import io.horizontalsystems.bankwallet.R

class WordsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    var items: List<String> = listOf()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
            ViewHolderWord(LayoutInflater.from(parent.context).inflate(R.layout.view_holder_word, parent, false) as TextView)

    override fun getItemCount() = items.size

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder) {
            is ViewHolderWord -> holder.textView.text = "${position + 1}. ${items[position]}"
        }
    }

    class ViewHolderWord(val textView: TextView) : RecyclerView.ViewHolder(textView)

}

package io.horizontalsystems.bankwallet.lib

import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.EditText
import io.horizontalsystems.bankwallet.R

class WordsInputAdapter(private val listener: EditTextViewHolder.WordsChangedListener) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    override fun getItemCount() = 12

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EditTextViewHolder {
        val editText = LayoutInflater.from(parent.context).inflate(R.layout.view_holder_input_word, parent, false) as EditText
        return EditTextViewHolder(editText, listener)
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        (holder as? EditTextViewHolder)?.bind(position)
    }

}

package io.horizontalsystems.bankwallet.viewHelpers

import android.view.View
import android.view.animation.Animation
import android.view.animation.Transformation

object AnimationHelper {

    fun expand(v: View) {
        v.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED))
        val targetHeight = v.measuredHeight

        v.alpha = 0.3f
        val a = object : Animation() {
            override fun applyTransformation(interpolatedTime: Float, t: Transformation) {
                v.visibility = View.VISIBLE
                v.layoutParams.height = if (interpolatedTime == 1f) targetHeight else (targetHeight * interpolatedTime).toInt()
                v.requestLayout()
                if (interpolatedTime > 0.3f) {
                    v.alpha = interpolatedTime
                }
            }

            override fun willChangeBounds(): Boolean {
                return true
            }
        }

        // 1dp/ms
        a.duration = (((targetHeight / v.context.resources.displayMetrics.density)) * 4).toLong()
        v.startAnimation(a)
    }

    fun collapse(v: View) {
        val initialHeight = v.measuredHeight

        val a = object : Animation() {
            override fun applyTransformation(interpolatedTime: Float, t: Transformation) {
                if (interpolatedTime == 1f) {
                    v.visibility = View.GONE
                } else {
                    v.layoutParams.height = initialHeight - (initialHeight * interpolatedTime).toInt()
                    v.requestLayout()
                    if (interpolatedTime > 0.3f) {
                        v.alpha = (1 - interpolatedTime)*2
                    }
                }
            }

            override fun willChangeBounds(): Boolean {
                return true
            }
        }

        // 1dp/ms
        a.duration = (((initialHeight / v.context.resources.displayMetrics.density)) * 4).toLong()
        v.startAnimation(a)
    }
}

package io.horizontalsystems.bankwallet.viewHelpers

import android.content.Context
import android.text.format.DateFormat
import io.horizontalsystems.bankwallet.R
import java.text.SimpleDateFormat
import java.util.*

object DateHelper {

    fun getOnlyTime(date: Date): String = formatDate(date, "HH:mm")
    fun getFullDateWithShortMonth(date: Date): String = formatDate(date, "MMM d, yyyy, HH:mm")

    fun getRelativeDateString(context: Context, date: Date): String {
        val secondsAgo = getSecondsAgo(date).toInt()
        val hoursAgo = secondsAgo / (60 * 60)
        val minutesAgo = secondsAgo / 60

        return when {
            secondsAgo < 60 -> context.getString(R.string.Timestamp_JustNow)
            secondsAgo < 60 * 60 -> context.getString(R.string.Timestamp_MinAgo, minutesAgo)
            hoursAgo < 12 -> context.getString(R.string.Timestamp_HoursAgo, hoursAgo)
            isToday(date) -> context.getString(R.string.Timestamp_Today)
            isYesterday(date) -> context.getString(R.string.Timestamp_Yesterday)
            isThisYear(date) -> getFormattedDateWithoutYear(date)
            else -> getFormattedDateWithYear(date)
        }
    }

    fun getShortDateForTransaction(date: Date): String = if (isThisYear(date)) {
        formatDate(date, "MMM d")
    } else {
        formatDate(date, "MM/dd/yyyy")
    }

    fun formatDateInUTC(timestamp: Long, dateFormat: String): String {
        val dateFormatter = SimpleDateFormat(dateFormat, Locale("US"))
        dateFormatter.timeZone = TimeZone.getTimeZone("UTC")
        return dateFormatter.format(Date(timestamp * 1000)) //timestamp in seconds
    }

    fun minutesAfterNow(minutes: Int): Long {
        var now = Date()
        val cal = Calendar.getInstance()
        cal.time = now
        cal.add(Calendar.MINUTE, minutes)
        now = cal.time
        return now.time
    }

    fun formatDate(date: Date, outputFormat: String) =
            SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), outputFormat)).format(date)

    private fun getFormattedDateWithYear(date: Date) = formatDate(date, "MMMM d, yyyy")

    private fun getFormattedDateWithoutYear(date: Date) = formatDate(date, "MMMM d")

    private fun getSecondsAgo(date: Date): Long {
        val differenceInMillis = Date().time - date.time
        return differenceInMillis / 1000
    }

    fun getSecondsAgo(dateInMillis: Long): Long {
        val differenceInMillis = Date().time - dateInMillis
        return differenceInMillis / 1000
    }

    private fun isToday(date: Date): Boolean {
        val today = Calendar.getInstance()
        return isSameDay(date, today)
    }

    private fun isYesterday(date: Date): Boolean {
        val yesterday = Calendar.getInstance()
        yesterday.add(Calendar.DAY_OF_YEAR, -1)
        return isSameDay(date, yesterday)
    }

    private fun isSameDay(date: Date, calendar: Calendar): Boolean {
        val calendar2 = Calendar.getInstance()
        calendar2.time = date

        return calendar.get(Calendar.YEAR) == calendar2.get(Calendar.YEAR) && calendar.get(Calendar.DAY_OF_YEAR) == calendar2.get(Calendar.DAY_OF_YEAR)
    }

    private fun isThisYear(date: Date): Boolean {
        val calendar = Calendar.getInstance()
        val calendar2 = Calendar.getInstance()
        calendar2.time = date

        return calendar.get(Calendar.YEAR) == calendar2.get(Calendar.YEAR)
    }

}

package io.horizontalsystems.bankwallet.viewHelpers

import android.graphics.PorterDuff
import android.support.v4.content.ContextCompat
import android.view.Gravity
import android.widget.TextView
import android.widget.Toast
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.core.App

object HudHelper {

    private var toast: Toast? = null

    fun showSuccessMessage(text: Int) {
        showHudNotification(text, R.color.green_crypto)
    }

    fun showErrorMessage(text: Int) {
        showHudNotification(text, R.color.red_warning)
    }

    private fun showHudNotification(text: Int, backgroundColor: Int) {
        toast?.cancel()

        val toast = Toast.makeText(App.instance, text, Toast.LENGTH_SHORT)

        val toastText = toast.view.findViewById(android.R.id.message) as TextView
        toastText.setTextColor(ContextCompat.getColor(toast.view.context, R.color.white))
        toast.view.background.setColorFilter(ContextCompat.getColor(toast.view.context, backgroundColor), PorterDuff.Mode.SRC_IN)
        toast.setGravity(Gravity.CENTER, 0, 0)
        toast.show()
    }
}

package io.horizontalsystems.bankwallet.viewHelpers

import android.content.Context
import android.content.res.Resources
import android.graphics.PorterDuff
import android.graphics.drawable.Drawable
import android.support.v4.content.ContextCompat
import android.support.v4.graphics.drawable.DrawableCompat
import android.util.TypedValue
import android.view.Menu
import io.horizontalsystems.bankwallet.R

object LayoutHelper {

    fun tintMenuIcons(menu: Menu, color: Int) {
        for (i in 0 until menu.size()) {
            val drawable = menu.getItem(i).icon
            if (drawable != null) {
                drawable.mutate()
                drawable.setColorFilter(color, PorterDuff.Mode.SRC_ATOP)
            }
        }
    }

    fun dp(dp: Float, context: Context?) = context?.let {
        val density = context.resources.displayMetrics.density
        if (dp == 0f) 0 else Math.ceil((density * dp).toDouble()).toInt()
    } ?: dp.toInt()

    fun getAttr(attr: Int, theme: Resources.Theme): Int? {
        val typedValue = TypedValue()
        return if (theme.resolveAttribute(attr, typedValue, true))
            typedValue.data
        else
            null
    }

    fun d(id: Int, context: Context): Drawable? {
        return ContextCompat.getDrawable(context, id)
    }

    fun getTintedDrawable(drawableResource: Int, colorId: Int, context: Context): Drawable? {
        val drawable = ContextCompat.getDrawable(context, drawableResource)?.let { DrawableCompat.wrap(it) }
        drawable?.mutate()
        val color = ContextCompat.getColor(context, colorId)
        drawable?.let { DrawableCompat.setTint(it, color) }
        drawable?.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
        return drawable
    }

    fun getCoinDrawableResource(coinCode: String): Int {
        return when(coinCode) {
            "BCH" -> R.drawable.coin_bitcoin_cash
            "LTC" -> R.drawable.coin_litecoin
            "DASH" -> R.drawable.coin_dash
            "EOS" -> R.drawable.coin_eos
            "XMR" -> R.drawable.coin_monero
            "XRP" -> R.drawable.coin_xrp
            "ZEC" -> R.drawable.coin_zcash
            "ETH" -> R.drawable.coin_ether
            "ADA" -> R.drawable.coin_cardano
            "XLM" -> R.drawable.coin_stellar
            "USDT" -> R.drawable.coin_tether
            else -> R.drawable.coin_bitcoin
        }
    }

    fun getInfoBadge(wordListBackedUp: Boolean, resources: Resources): Drawable? {
        var infoBadge: Drawable? = null
        if (!wordListBackedUp) {
            infoBadge = resources.getDrawable(R.drawable.info, null)
            infoBadge?.setTint(resources.getColor(R.color.red_warning, null))
        }
        return infoBadge
    }

}

package io.horizontalsystems.bankwallet.viewHelpers

import java.text.NumberFormat

object NumberFormatHelper {

    val fiatAmountFormat: NumberFormat
        get() {
            val numberFormat = NumberFormat.getInstance()
            numberFormat.maximumFractionDigits = 2
            numberFormat.minimumFractionDigits = 2
            return numberFormat
        }

    val cryptoAmountFormat: NumberFormat
        get() {
            val numberFormat = NumberFormat.getInstance()
            numberFormat.maximumFractionDigits = 12
            numberFormat.minimumFractionDigits = 2
            return numberFormat
        }

}

package io.horizontalsystems.bankwallet.viewHelpers

import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.graphics.Bitmap
import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.MultiFormatWriter
import com.google.zxing.WriterException
import com.journeyapps.barcodescanner.BarcodeEncoder
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.core.IClipboardManager

object TextHelper : IClipboardManager {

    override val hasPrimaryClip: Boolean
        get() = clipboard?.hasPrimaryClip() ?: false

    override fun copyText(text: String) {
        copyTextToClipboard(App.instance, text)
    }

    override fun getCopiedText(): String {
        return clipboard?.primaryClip?.itemCount?.let { count ->
            if (count > 0) {
                clipboard?.primaryClip?.getItemAt(0)?.text?.toString()
            } else {
                null
            }
        } ?: ""
    }

    fun getQrCodeBitmapFromAddress(address: String): Bitmap? {
        val multiFormatWriter = MultiFormatWriter()
        return try {
            val imageSize = LayoutHelper.dp(150F, App.instance)
            val bitMatrix = multiFormatWriter.encode(address, BarcodeFormat.QR_CODE, imageSize, imageSize, hashMapOf(EncodeHintType.MARGIN to 0))
            val barcodeEncoder = BarcodeEncoder()
            val bitmap = barcodeEncoder.createBitmap(bitMatrix)
            bitmap
        } catch (e: WriterException) {
            e.printStackTrace()
            null
        }
    }

    fun getCleanCoinCode(coin: String): String {
        var cleanedCoin = coin.removeSuffix("t")
        cleanedCoin = cleanedCoin.removeSuffix("r")
        return cleanedCoin
    }

    private val clipboard: ClipboardManager?
        get() = App.instance.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager

    private fun copyTextToClipboard(context: Context, text: String) {
        val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager
        val clip = ClipData.newPlainText("text", text)
        clipboard?.primaryClip = clip
    }

}

package io.horizontalsystems.bankwallet.viewHelpers

import android.support.v4.content.ContextCompat
import android.text.SpannableString
import android.text.style.ForegroundColorSpan
import android.text.style.RelativeSizeSpan
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.entities.CoinValue
import io.horizontalsystems.bankwallet.entities.CurrencyValue
import java.math.BigDecimal
import java.math.RoundingMode
import java.text.NumberFormat

object ValueFormatter {

    private val COIN_BIG_NUMBER_EDGE = "0.0001".toBigDecimal()
    private val FIAT_BIG_NUMBER_EDGE = "100".toBigDecimal()
    private val FIAT_SMALL_NUMBER_EDGE = "0.01".toBigDecimal()

    private val currencyFormatter: NumberFormat
        get() {
            return NumberFormat.getInstance()
        }

    fun format(coinValue: CoinValue, explicitSign: Boolean = false, realNumber: Boolean = false): String? {
        var value = if (explicitSign) coinValue.value.abs() else coinValue.value

        value = when {
            !realNumber && value >= COIN_BIG_NUMBER_EDGE -> value.setScale(4, RoundingMode.HALF_EVEN)
            value.compareTo(BigDecimal.ZERO) == 0 -> value.setScale(0, RoundingMode.HALF_EVEN)
            else -> value.setScale(8, RoundingMode.HALF_EVEN)
        }
        value = value.stripTrailingZeros()

        var result = "${value.toPlainString()} ${coinValue.coinCode}"

        if (explicitSign) {
            val sign = if (coinValue.value < BigDecimal.ZERO) "-" else "+"
            result = "$sign $result"
        }

        return result
    }

    private val coinFormatter: NumberFormat
        get() {
            val format: NumberFormat = NumberFormat.getInstance()
            format.maximumFractionDigits = 8
            return format
        }

    fun format(value: Double): String {
        val customFormatter = coinFormatter
        if (value == 0.0) {
            customFormatter.maximumFractionDigits = 0
        }

        return customFormatter.format(value)
    }

    fun format(currencyValue: CurrencyValue, approximate: Boolean = false, showNegativeSign: Boolean = true, realNumber: Boolean = false): String? {
        var value = currencyValue.value

        value = value.abs()

        var result: String = when {
            value.compareTo(BigDecimal.ZERO) == 0 -> "0"
            value < FIAT_SMALL_NUMBER_EDGE -> "0.01"
            else -> {
                value = when {
                    !realNumber && (value >= FIAT_BIG_NUMBER_EDGE || approximate) -> value.setScale(0, RoundingMode.HALF_EVEN)
                    else -> value.setScale(2, RoundingMode.HALF_EVEN)
                }
                currencyFormatter.format(value)
            }
        }

        result = "${currencyValue.currency.symbol} $result"

        if (showNegativeSign && currencyValue.value < BigDecimal.ZERO) {
            result = "- $result"
        }

        if (approximate) {
            result = "~ $result"
        }

        return result
    }

    fun formatForTransactions(currencyValue: CurrencyValue, isIncoming: Boolean): SpannableString {
        val spannable = SpannableString(format(currencyValue))

        //set currency sign size
        val endOffset = if (currencyValue.value < BigDecimal.ZERO) 3 else 1
        spannable.setSpan(RelativeSizeSpan(0.75f), 0, endOffset, 0)

        //set color
        val amountTextColor = if (isIncoming) R.color.green_crypto else R.color.yellow_crypto
        val color = ContextCompat.getColor(App.instance, amountTextColor)

        spannable.setSpan(ForegroundColorSpan(color), 0, spannable.length, 0)
        return spannable
    }

    fun formatSimple(currencyValue: CurrencyValue): String? {
        val result = currencyValue.value.setScale(2, RoundingMode.HALF_EVEN)
        val formatted = currencyFormatter.format(result)
        return "${currencyValue.currency.symbol}$formatted"
    }
}

package io.horizontalsystems.bankwallet.core.factories

import io.horizontalsystems.bankwallet.core.BitcoinAdapter
import io.horizontalsystems.bankwallet.core.EthereumAdapter
import io.horizontalsystems.bankwallet.core.IAppConfigProvider
import io.horizontalsystems.bankwallet.entities.AuthData
import io.horizontalsystems.bankwallet.entities.CoinType

class AdapterFactory(private val appConfigProvider: IAppConfigProvider) {

    fun adapterForCoin(coinType: CoinType, authData: AuthData, newWallet: Boolean) = when (coinType) {
            is CoinType.Bitcoin -> BitcoinAdapter.createBitcoin(authData.words, appConfigProvider.testMode, newWallet, authData.walletId)
            is CoinType.BitcoinCash -> BitcoinAdapter.createBitcoinCash(authData.words, appConfigProvider.testMode, newWallet, authData.walletId)
            is CoinType.Ethereum -> EthereumAdapter.createEthereum(authData.words, appConfigProvider.testMode)
            is CoinType.Erc20 -> null
    }

}

package io.horizontalsystems.bankwallet.core.factories

import io.horizontalsystems.bankwallet.core.ICurrentDateProvider
import io.horizontalsystems.bankwallet.core.ILockoutUntilDateFactory
import java.util.*

class LockoutUntilDateFactory(private val currentDateProvider: ICurrentDateProvider) : ILockoutUntilDateFactory {

    override fun lockoutUntilDate(failedAttempts: Int, lockoutTimestamp: Long, uptime: Long): Date? {

        var timeFrame: Long = 0

        when {
            failedAttempts == 5 -> timeFrame = 5 * 60 * 1000 - (uptime - lockoutTimestamp)
            failedAttempts == 6 -> timeFrame = 10 * 60 * 1000 - (uptime - lockoutTimestamp)
            failedAttempts == 7 -> timeFrame = 15 * 60 * 1000 - (uptime - lockoutTimestamp)
            failedAttempts >= 8 -> timeFrame = 30 * 60 * 1000 - (uptime - lockoutTimestamp)
        }

        return when {
            timeFrame > 0 -> {
                val timestamp = timeFrame + currentDateProvider.currentDate.time
                val date = currentDateProvider.currentDate
                date.time = timestamp
                date
            }
            else -> null
        }
    }

}

package io.horizontalsystems.bankwallet.core.factories

import io.horizontalsystems.bankwallet.core.ICurrencyManager
import io.horizontalsystems.bankwallet.core.IWalletManager
import io.horizontalsystems.bankwallet.core.managers.RateManager
import io.horizontalsystems.bankwallet.entities.CoinValue
import io.horizontalsystems.bankwallet.entities.CurrencyValue
import io.horizontalsystems.bankwallet.entities.TransactionItem
import io.horizontalsystems.bankwallet.modules.transactions.TransactionStatus
import io.horizontalsystems.bankwallet.modules.transactions.TransactionViewItem
import java.math.BigDecimal
import java.util.*

class TransactionViewItemFactory(
        private val walletManager: IWalletManager,
        private val currencyManager: ICurrencyManager,
        private val rateManager: RateManager) {

    private val latestRateFallbackThreshold: Long = 60 // minutes

    fun item(transactionItem: TransactionItem, lastBlockHeight: Int?, threshold: Int?, rate: CurrencyValue?): TransactionViewItem {
        val record = transactionItem.record

        var status: TransactionStatus = TransactionStatus.Pending

        if (record.blockHeight != 0L && lastBlockHeight != null) {

            val confirmations = lastBlockHeight - record.blockHeight + 1

            if (confirmations >= 0) {
                status = when {
                    confirmations >= threshold ?: 1 -> TransactionStatus.Completed
                    else -> TransactionStatus.Processing(confirmations.toInt())
                }
            }
        }

        val incoming = record.amount > BigDecimal.ZERO

        val toAddress = when (incoming) {
            true -> record.to.find { it.mine }?.address
            false -> record.to.find { !it.mine }?.address ?: record.to.find { it.mine }?.address
        }

        val currencyValue = rate?.let { CurrencyValue(it.currency, record.amount * it.value) }

        return TransactionViewItem(
                record.transactionHash,
                CoinValue(transactionItem.coinCode, record.amount),
                currencyValue,
                record.from.firstOrNull { it.mine != incoming }?.address,
                toAddress,
                incoming,
                if (record.timestamp == 0L) null else Date(record.timestamp * 1000),
                status
        )
    }
}

package io.horizontalsystems.bankwallet.core.factories

import io.horizontalsystems.bankwallet.entities.AuthData
import io.horizontalsystems.bankwallet.entities.Coin
import io.horizontalsystems.bankwallet.entities.Wallet

class WalletFactory(private val adapterFactory: AdapterFactory) {

    fun createWallet(coin: Coin, authData: AuthData): Wallet? {
        val adapter = adapterFactory.adapterForCoin(coin.type, authData, false) ?: return null
        adapter.start()

        return Wallet(coin.title, coin.code, adapter)
    }

}

package io.horizontalsystems.bankwallet.core.managers

import io.reactivex.subjects.PublishSubject

class AppCloseManager{
    var appCloseSignal = PublishSubject.create<Unit>()
}

package io.horizontalsystems.bankwallet.core.managers

import io.horizontalsystems.bankwallet.BuildConfig
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.core.IAppConfigProvider
import io.horizontalsystems.bankwallet.entities.Coin
import io.horizontalsystems.bankwallet.entities.CoinType
import io.horizontalsystems.bankwallet.entities.Currency

class AppConfigProvider : IAppConfigProvider {

    override val testMode: Boolean = BuildConfig.testMode

    override val currencies: List<Currency> = listOf(
            Currency(code = "USD", symbol = "\u0024"),
            Currency(code = "EUR", symbol = "\u20AC"),
            Currency(code = "RUB", symbol = "\u20BD"),
            Currency(code = "AUD", symbol = "\u20B3"),
            Currency(code = "CAD", symbol = "\u0024"),
            Currency(code = "CHF", symbol = "\u20A3"),
            Currency(code = "CNY", symbol = "\u00A5"),
            Currency(code = "GBP", symbol = "\u00A3"),
            Currency(code = "JPY", symbol = "\u00A5")
    )

    override val localizations: List<String>
        get() {
            val coinsString = App.instance.getString(R.string.localizations)
            return coinsString.split(",")
        }

    override val defaultCoins: List<Coin>
        get() {
            val suffix = if (testMode) "t" else ""
            val coins = mutableListOf<Coin>()
            coins.add(Coin("Bitcoin", "BTC$suffix", CoinType.Bitcoin))
            coins.add(Coin("Bitcoin Cash", "BCH$suffix", CoinType.BitcoinCash))
            coins.add(Coin("Ethereum", "ETH$suffix", CoinType.Ethereum))
            return coins
        }
}

package io.horizontalsystems.bankwallet.core.managers

import android.security.keystore.UserNotAuthenticatedException
import io.horizontalsystems.bankwallet.core.*
import io.horizontalsystems.bankwallet.entities.AuthData
import io.reactivex.subjects.PublishSubject

class AuthManager(
        private val secureStorage: ISecuredStorage,
        private val localStorage: ILocalStorage,
        private val coinManager: ICoinManager,
        private val rateManager: RateManager) {

    var walletManager: IWalletManager? = null
    var pinManager: IPinManager? = null

    var authData: AuthData? = null
    var authDataSignal = PublishSubject.create<Unit>()

    var isLoggedIn: Boolean = false
        get() = !secureStorage.noAuthData()

    @Throws(UserNotAuthenticatedException::class)
    fun safeLoad() {
        authData = secureStorage.authData
        authDataSignal.onNext(Unit)
    }

    @Throws(UserNotAuthenticatedException::class)
    fun login(words: List<String>) {
        AuthData(words).let {
            secureStorage.saveAuthData(it)
            authData = it
            coinManager.enableDefaultCoins()
            walletManager?.initWallets()
        }
    }

    fun logout() {
        walletManager?.clear()
        pinManager?.clear()
        localStorage.clear()
        coinManager.clear()
        rateManager.clear()

//        todo: clear authData from secureStorage. note clearing localstorage also clears auth data
//        secureStorage.clearAuthData()
        authData = null

    }

}

package io.horizontalsystems.bankwallet.core.managers

import android.app.Activity
import android.app.Application
import android.os.Bundle
import io.horizontalsystems.bankwallet.core.App

class BackgroundManager(application: Application) : Application.ActivityLifecycleCallbacks {

    init {
        application.registerActivityLifecycleCallbacks(this)
    }

    private var refs: Int = 0

    val inForeground: Boolean
        get() = refs > 0

    val inBackground: Boolean
        get() = refs == 0

    override fun onActivityStarted(activity: Activity?) {
        if(refs == 0) {
            App.lockManager.willEnterForeground()
        }
        refs++
    }

    override fun onActivityStopped(activity: Activity?) {
        refs--

        if (refs == 0) {
            //App is in background
            App.lockManager.didEnterBackground()
        }
    }

    override fun onActivityCreated(activity: Activity?, savedInstanceState: Bundle?) {}

    override fun onActivityPaused(p0: Activity?) {}

    override fun onActivityResumed(p0: Activity?) {}

    override fun onActivitySaveInstanceState(activity: Activity?, outState: Bundle?) {}

    override fun onActivityDestroyed(activity: Activity?) {}

}

package io.horizontalsystems.bankwallet.core.managers

import io.horizontalsystems.bankwallet.core.IAppConfigProvider
import io.horizontalsystems.bankwallet.core.ICoinManager
import io.horizontalsystems.bankwallet.core.ICoinStorage
import io.horizontalsystems.bankwallet.entities.Coin
import io.reactivex.Flowable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.PublishSubject

class CoinManager(private val appConfigProvider: IAppConfigProvider, private val coinStorage: ICoinStorage) : ICoinManager {

    override val coinsUpdatedSignal: PublishSubject<Unit> = PublishSubject.create()

    init {
        val disposable = coinStorage.enabledCoinsObservable()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe {
                    coins = it
                }
    }

    override var coins: List<Coin> = listOf()
        set(value) {
            field = value
            coinsUpdatedSignal.onNext(Unit)
        }

    override val allCoinsObservable: Flowable<List<Coin>>
        get() = coinStorage.allCoinsObservable().flatMap { allCoinsFromDb ->
            val allCoins = allCoinsFromDb.toMutableList()
            appConfigProvider.defaultCoins.forEach { coin ->
                if (!allCoinsFromDb.contains(coin)) {
                    allCoins.add(coin)
                }
            }
            return@flatMap Flowable.just(allCoins)
        }

    override fun enableDefaultCoins() {
        coinStorage.save(appConfigProvider.defaultCoins)
    }

    override fun clear() {
        coins = listOf()
        coinStorage.deleteAll()
    }
}

package io.horizontalsystems.bankwallet.core.managers

import io.horizontalsystems.bankwallet.core.IAppConfigProvider
import io.horizontalsystems.bankwallet.core.ICurrencyManager
import io.horizontalsystems.bankwallet.core.ILocalStorage
import io.horizontalsystems.bankwallet.entities.Currency
import io.reactivex.subjects.PublishSubject

class CurrencyManager(private val localStorage: ILocalStorage, private val appConfigProvider: IAppConfigProvider) : ICurrencyManager {

    override val baseCurrency: Currency
        get() {
            val currencies = appConfigProvider.currencies
            val storedCode = localStorage.baseCurrencyCode
            return storedCode?.let { code ->
                currencies.first { it.code == code }
            } ?: currencies.first()
        }

    override val currencies: List<Currency>
        get() = appConfigProvider.currencies

    override val baseCurrencyUpdatedSignal = PublishSubject.create<Unit>()

    override fun setBaseCurrency(code: String) {
        localStorage.baseCurrencyCode = code

        baseCurrencyUpdatedSignal.onNext(Unit)
    }

}

package io.horizontalsystems.bankwallet.core.managers

import io.horizontalsystems.bankwallet.core.ICurrentDateProvider
import java.util.*

class CurrentDateProvider: ICurrentDateProvider {

    override val currentDate: Date
        get() = Date()

}

package io.horizontalsystems.bankwallet.core.managers

import android.os.Build
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.core.IAppConfigProvider
import io.horizontalsystems.bankwallet.core.ILanguageManager
import io.horizontalsystems.bankwallet.core.ILocalStorage
import java.util.*

class LanguageManager(
        private val localStorage: ILocalStorage,
        private val appConfigProvider: IAppConfigProvider,
        fallbackLanguage: Locale) : ILanguageManager {

    override var preferredLanguage: Locale? = null
        get() {
            return when {
                Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> App.instance.resources.configuration.locales.get(0)
                else -> App.instance.resources.configuration.locale
            }
        }

    override val availableLanguages: List<Locale>
        get() = appConfigProvider.localizations.map { Locale(it) }

    private var language: Locale = localStorage.currentLanguage?.let { Locale(localStorage.currentLanguage) }
            ?: preferredLanguage ?: fallbackLanguage

    override var currentLanguage: Locale
        get() = language
        set(value) {
            language = value
            localStorage.currentLanguage = value.language

            val configuration = App.instance.resources.configuration
            configuration.setLocale(value)

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                App.instance.createConfigurationContext(configuration)
            } else {
                val displayMetrics = App.instance.resources.displayMetrics
                App.instance.resources.updateConfiguration(configuration, displayMetrics)
            }
        }

}

package io.horizontalsystems.bankwallet.core.managers

import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.core.ILocalStorage
import io.horizontalsystems.bankwallet.modules.send.SendModule

class LocalStorageManager : ILocalStorage {

    private val CURRENT_LANGUAGE = "current_language"
    private val LIGHT_MODE_ENABLED = "light_mode_enabled"
    private val FINGERPRINT_ENABLED = "fingerprint_enabled"
    private val SEND_INPUT_TYPE = "send_input_type"
    private val WORDLIST_BACKUP = "wordlist_backup"
    private val I_UNDERSTAND = "i_understand"
    private val BLOCK_TILL_DATE = "unblock_date"
    private val BASE_CURRENCY_CODE = "base_currency_code"
    private val NEW_WALLET = "new_wallet"
    private val FAILED_ATTEMPTS = "failed_attempts"
    private val LOCKOUT_TIMESTAMP = "lockout_timestamp"
    private val BASE_BITCOIN_PROVIDER = "base_bitcoin_provider"
    private val BASE_ETHEREUM_PROVIDER = "base_ethereum_provider"

    override var currentLanguage: String?
        get() = App.preferences.getString(CURRENT_LANGUAGE, null)
        set(language) {
            App.preferences.edit().putString(CURRENT_LANGUAGE, language).apply()
        }

    override var isBackedUp: Boolean
        get() = App.preferences.getBoolean(WORDLIST_BACKUP, false)
        set(backedUp) {
            App.preferences.edit().putBoolean(WORDLIST_BACKUP, backedUp).apply()
        }

    override var isBiometricOn: Boolean
        get() = App.preferences.getBoolean(FINGERPRINT_ENABLED, false)
        set(enabled) {
            App.preferences.edit().putBoolean(FINGERPRINT_ENABLED, enabled).apply()
        }

    override var sendInputType: SendModule.InputType?
        get() = App.preferences.getString(SEND_INPUT_TYPE, null)?.let {
            try {
                SendModule.InputType.valueOf(it)
            } catch (e: IllegalArgumentException) {
                null
            }
        }
        set(value) {
            val editor = App.preferences.edit()
            when (value) {
                null -> editor.remove(SEND_INPUT_TYPE).apply()
                else -> editor.putString(SEND_INPUT_TYPE, value.name).apply()
            }
        }

    override var isLightModeOn: Boolean
        get() = App.preferences.getBoolean(LIGHT_MODE_ENABLED, false)
        set(enabled) {
            App.preferences.edit().putBoolean(LIGHT_MODE_ENABLED, enabled).apply()
        }

    override var iUnderstand: Boolean
        get() = App.preferences.getBoolean(I_UNDERSTAND, false)
        set(value) {
            App.preferences.edit().putBoolean(I_UNDERSTAND, value).apply()
        }

    override var baseCurrencyCode: String?
        get() = App.preferences.getString(BASE_CURRENCY_CODE, null)
        set(value) {
            App.preferences.edit().putString(BASE_CURRENCY_CODE, value).apply()
        }

    override var blockTillDate: Long?
        get() {
            val date = App.preferences.getLong(BLOCK_TILL_DATE, 0)
            return if (date > 0) date else null
        }
        set(date) {
            date?.let {
                App.preferences.edit().putLong(BLOCK_TILL_DATE, date).apply()
            } ?: run {
                App.preferences.edit().remove(BLOCK_TILL_DATE).apply()
            }
        }

    override var isNewWallet: Boolean
        get() = App.preferences.getBoolean(NEW_WALLET, false)
        set(value) {
            App.preferences.edit().putBoolean(NEW_WALLET, value).apply()
        }

    override var failedAttempts: Int?
        get() {
            val attempts = App.preferences.getInt(FAILED_ATTEMPTS, 0)
            return when (attempts) {
                0 -> null
                else -> attempts
            }
        }
        set(value) {
            value?.let {
                App.preferences.edit().putInt(FAILED_ATTEMPTS, it).apply()
            } ?: App.preferences.edit().remove(FAILED_ATTEMPTS).apply()
        }

    override var lockoutUptime: Long?
        get() {
            val timestamp = App.preferences.getLong(LOCKOUT_TIMESTAMP, 0L)
            return when (timestamp) {
                0L -> null
                else -> timestamp
            }
        }
        set(value) {
            value?.let {
                App.preferences.edit().putLong(LOCKOUT_TIMESTAMP, it).apply()
            } ?: App.preferences.edit().remove(LOCKOUT_TIMESTAMP).apply()
        }

    override var baseBitcoinProvider: String?
        get() = App.preferences.getString(BASE_BITCOIN_PROVIDER, null)
        set(value) {
            App.preferences.edit().putString(BASE_BITCOIN_PROVIDER, value).apply()
        }

    override var baseEthereumProvider: String?
        get() = App.preferences.getString(BASE_ETHEREUM_PROVIDER, null)
        set(value) {
            App.preferences.edit().putString(BASE_ETHEREUM_PROVIDER, value).apply()
        }

    override fun clear() {
        App.preferences.edit().clear().apply()
    }

}

package io.horizontalsystems.bankwallet.core.managers

import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.core.ILockManager
import io.horizontalsystems.bankwallet.core.ISecuredStorage
import io.horizontalsystems.bankwallet.modules.pin.PinModule
import io.horizontalsystems.bankwallet.viewHelpers.DateHelper
import java.util.*

class LockManager(
        private val securedStorage: ISecuredStorage,
        private val authManager: AuthManager): ILockManager {

    private val lockTimeout: Double = 60.0

    override var isLocked: Boolean = false

    override fun didEnterBackground() {
        if (!authManager.isLoggedIn || isLocked) {
            return
        }

        App.lastExitDate = Date().time
    }

    override fun willEnterForeground() {
        if (!authManager.isLoggedIn || isLocked || securedStorage.pinIsEmpty()) {
            return
        }

        val secondsAgo = DateHelper.getSecondsAgo(App.lastExitDate)
        if (secondsAgo < lockTimeout) {
            return
        }

        lock()
    }

    override fun lock() {
        isLocked = true
        PinModule.startForUnlock()
    }

    override fun onUnlock() {
        isLocked = false
    }
}

package io.horizontalsystems.bankwallet.core.managers

import io.horizontalsystems.bankwallet.core.ILocalStorage
import io.horizontalsystems.bankwallet.core.ILockoutManager
import io.horizontalsystems.bankwallet.core.ILockoutUntilDateFactory
import io.horizontalsystems.bankwallet.core.IUptimeProvider
import io.horizontalsystems.bankwallet.entities.LockoutState

class LockoutManager(
        private val localStorage: ILocalStorage,
        private val uptimeProvider: IUptimeProvider,
        private val lockoutUntilDateFactory: ILockoutUntilDateFactory) : ILockoutManager {

    private val lockoutThreshold = 5

    override val currentState: LockoutState
        get() {
            val failedAttempts = localStorage.failedAttempts
            val attemptsLeft = failedAttempts?.let {
                if (it >= lockoutThreshold) {
                    val lockoutUptime = localStorage.lockoutUptime ?: uptimeProvider.uptime
                    lockoutUntilDateFactory.lockoutUntilDate(it, lockoutUptime, uptimeProvider.uptime)?.let { untilDate ->
                        return LockoutState.Locked(untilDate)
                    }
                }

                //calculate attempts left
                val attempts = lockoutThreshold - it
                if (attempts < 1) 1 else attempts
            }

            return LockoutState.Unlocked(attemptsLeft)
        }

    override fun didFailUnlock() {
        val attempts = (localStorage.failedAttempts ?: 0) + 1
        if (attempts >= lockoutThreshold) {
            localStorage.lockoutUptime = uptimeProvider.uptime
        }
        localStorage.failedAttempts = attempts
    }

    override fun dropFailedAttempts() {
        localStorage.failedAttempts = null
    }

}

package io.horizontalsystems.bankwallet.core.managers

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import io.horizontalsystems.bankwallet.core.App
import io.reactivex.subjects.PublishSubject

class NetworkAvailabilityManager {

    private val connectivityManager = App.instance.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

    var isConnected = connectivityManager.activeNetworkInfo?.isConnected ?: false
    val networkAvailabilitySignal = PublishSubject.create<Unit>()

    init {
        App.instance.registerReceiver(object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                onUpdateStatus()
            }
        }, IntentFilter("android.net.conn.CONNECTIVITY_CHANGE"))
    }

    private fun onUpdateStatus() {
        val newIsConnected = connectivityManager.activeNetworkInfo?.isConnected ?: false

        if (isConnected != newIsConnected) {
            isConnected = newIsConnected
            networkAvailabilitySignal.onNext(Unit)
        }
    }

}

package io.horizontalsystems.bankwallet.core.managers

import com.google.gson.GsonBuilder
import com.google.gson.JsonObject
import io.horizontalsystems.bankwallet.core.INetworkManager
import io.horizontalsystems.bankwallet.entities.LatestRate
import io.horizontalsystems.bankwallet.viewHelpers.DateHelper
import io.horizontalsystems.bankwallet.viewHelpers.TextHelper
import io.reactivex.Flowable
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.Path
import retrofit2.http.Url
import java.math.BigDecimal
import java.util.concurrent.TimeUnit

class NetworkManager : INetworkManager {

    override fun getRate(coinCode: String, currency: String, timestamp: Long): Flowable<BigDecimal> {
        val cleanedCoin = TextHelper.getCleanCoinCode(coinCode)

        return ServiceExchangeApi.service
                .getRatesByHour(cleanedCoin, currency, DateHelper.formatDateInUTC(timestamp, "yyyy/MM/dd/HH"))
                .flatMap { minuteRates ->
                    val minute = DateHelper.formatDateInUTC(timestamp, "mm")
                    Flowable.just(minuteRates.getValue(minute))
                }
                .onErrorResumeNext(
                        ServiceExchangeApi.service
                                .getRate(cleanedCoin, currency, DateHelper.formatDateInUTC(timestamp, "yyyy/MM/dd"))
                                .onErrorResumeNext(Flowable.empty())
                )
    }

    override fun getLatestRate(coin: String, currency: String): Flowable<LatestRate> {
        val cleanedCoin = TextHelper.getCleanCoinCode(coin)
        return ServiceExchangeApi.service
                .getLatestRate(cleanedCoin, currency)
                .onErrorResumeNext(Flowable.empty())
    }

    override fun getTransaction(host: String, path: String): Flowable<JsonObject> {
        return ServiceFullTransaction.service(host)
                .getFullTransaction(path)
    }

    override fun ping(host: String, url: String): Flowable<JsonObject> {
        return ServicePing.service(host)
                .ping(url)
    }
}

object ServiceExchangeApi {

    val service: IExchangeRate = APIClient
            .retrofit("https://ipfs.horizontalsystems.xyz/ipns/Qmd4Gv2YVPqs6dmSy1XEq7pQRSgLihqYKL2JjK7DMUFPVz/io-hs/data/xrates/")
            .create(IExchangeRate::class.java)

    interface IExchangeRate {

        @GET("{coin}/{fiat}/{datePath}/index.json")
        fun getRate(
                @Path("coin") coinCode: String,
                @Path("fiat") currency: String,
                @Path("datePath") datePath: String
        ): Flowable<BigDecimal>

        @GET("{coin}/{fiat}/{datePath}/index.json")
        fun getRatesByHour(
                @Path("coin") coinCode: String,
                @Path("fiat") currency: String,
                @Path("datePath") datePath: String
        ): Flowable<Map<String, BigDecimal>>

        @GET("{coin}/{fiat}/index.json")
        fun getLatestRate(
                @Path("coin") coinCode: String,
                @Path("fiat") currency: String
        ): Flowable<LatestRate>

    }
}

object ServiceFullTransaction {
    fun service(apiURL: String): FullTransactionAPI {
        return APIClient.retrofit(apiURL)
                .create(FullTransactionAPI::class.java)
    }

    interface FullTransactionAPI {
        @GET
        @Headers("Content-Type: application/json")
        fun getFullTransaction(@Url path: String): Flowable<JsonObject>
    }

}

object ServicePing {
    fun service(apiURL: String): FullTransactionAPI {
        return APIClient.retrofit(apiURL, timeout = 8)
                .create(FullTransactionAPI::class.java)
    }

    interface FullTransactionAPI {
        @GET
        fun ping(@Url path: String): Flowable<JsonObject>
    }
}

object APIClient {
    fun retrofit(apiURL: String, timeout: Long = 60): Retrofit {

        val logger = HttpLoggingInterceptor().apply {
            level = HttpLoggingInterceptor.Level.BASIC
        }

        val httpClient = OkHttpClient.Builder()
        httpClient.addInterceptor(logger)
        httpClient.connectTimeout(timeout, TimeUnit.SECONDS)
        httpClient.readTimeout(timeout, TimeUnit.SECONDS)

        val gsonBuilder = GsonBuilder().setLenient()

        return Retrofit.Builder()
                .baseUrl(apiURL)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create(gsonBuilder.create()))
                .client(httpClient.build())
                .build()
    }
}

package io.horizontalsystems.bankwallet.core.managers

import io.horizontalsystems.bankwallet.core.IOneTimerDelegate
import java.util.*

class OneTimeTimer {

    var delegate: IOneTimerDelegate? = null
    private var timer: Timer? = null

    fun schedule(time: Date) {

        if(timer == null) {
            timer = Timer()
        }

        timer?.schedule(object : TimerTask() {
            override fun run() {
                delegate?.onFire()
            }
        }, time)

    }
}

package io.horizontalsystems.bankwallet.core.managers

import android.security.keystore.UserNotAuthenticatedException
import io.horizontalsystems.bankwallet.core.IPinManager
import io.horizontalsystems.bankwallet.core.ISecuredStorage

class PinManager(private val securedStorage: ISecuredStorage): IPinManager {

    override var pin: String? = null

    override fun safeLoad() {
        pin = securedStorage.savedPin
    }

    override val isPinSet: Boolean
        get() = !securedStorage.pinIsEmpty()

    @Throws(UserNotAuthenticatedException::class)
    override fun store(pin: String) {
        securedStorage.savePin(pin)
        this.pin = pin
    }

    override fun validate(pin: String): Boolean {
        return this.pin == pin
    }

    override fun clear() {
        pin = null
    }
}

package io.horizontalsystems.bankwallet.core.managers

import io.horizontalsystems.bankwallet.core.IRandomProvider
import java.util.*

class RandomProvider : IRandomProvider {

    override fun getRandomIndexes(count: Int): List<Int> {
        val indexes = mutableListOf<Int>()

        val random = Random()

        while (indexes.size < count) {
            val index = random.nextInt(12) + 1

            if (!indexes.contains(index)) {
                indexes.add(index)
            }
        }

        return indexes
    }

}

package io.horizontalsystems.bankwallet.core.managers

import io.horizontalsystems.bankwallet.core.INetworkManager
import io.horizontalsystems.bankwallet.core.IRateStorage
import io.horizontalsystems.bankwallet.entities.Rate
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode
import io.reactivex.Flowable
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import java.math.BigDecimal

class RateManager(private val storage: IRateStorage, private val networkManager: INetworkManager) {

    private var disposables: CompositeDisposable = CompositeDisposable()
    private var refreshDisposables: CompositeDisposable = CompositeDisposable()

    fun refreshLatestRates(coinCodes: List<String>, currencyCode: String) {
        refreshDisposables.clear()

        refreshDisposables.add(Flowable.mergeDelayError(
                coinCodes.map { coinCode ->
                    networkManager.getLatestRate(coinCode, currencyCode)
                            .map {
                                Rate(coinCode, currencyCode, it.value, it.timestamp, true)
                            }
                })
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .subscribe({
                    storage.saveLatest(it)
                }, {

                }))
    }

    fun refreshZeroRates(currencyCode: String) {
        disposables.add(storage.zeroRatesObservable(currencyCode)
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .subscribe { rates ->
                    rates.forEach { rate ->
                        retrieveFromNetwork(rate.coinCode, rate.currencyCode, rate.timestamp)
                    }
                }
        )
    }

    fun rateValueObservable(coinCode: CoinCode, currencyCode: String, timestamp: Long): Flowable<BigDecimal> {
        return storage.rateObservable(coinCode, currencyCode, timestamp)
                .flatMap {
                    val rate = it.firstOrNull()

                    if (rate == null) {
                        storage.save(Rate(coinCode, currencyCode, BigDecimal.ZERO, timestamp, false))
                        retrieveFromNetwork(coinCode, currencyCode, timestamp)
                    }

                    if (rate != null && rate.value != BigDecimal.ZERO) {
                        Flowable.just(rate.value)
                    } else if (timestamp < ((System.currentTimeMillis() / 1000) - 3600)) {
                        Flowable.empty()
                    } else {
                        storage.latestRateObservable(coinCode, currencyCode)
                                .flatMap {
                                    if (it.expired) {
                                        Flowable.empty<BigDecimal>()
                                    } else {
                                        Flowable.just(it.value)
                                    }
                                }

                    }
                }
                .distinctUntilChanged()
    }

    private fun retrieveFromNetwork(coinCode: CoinCode, currencyCode: String, timestamp: Long) {
        disposables.add(networkManager.getRate(coinCode, currencyCode, timestamp)
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .subscribe { rateValue ->
                    storage.save(Rate(coinCode, currencyCode, rateValue, timestamp, false))
                })
    }

    fun clear() {
        storage.deleteAll()
    }

}

package io.horizontalsystems.bankwallet.core.managers

import io.horizontalsystems.bankwallet.core.ICurrencyManager
import io.horizontalsystems.bankwallet.core.IWalletManager
import io.reactivex.Observable
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import java.util.concurrent.TimeUnit

class RateSyncer(private val rateManager: RateManager,
                 private val walletManager: IWalletManager,
                 private val currencyManager: ICurrencyManager,
                 private val networkAvailabilityManager: NetworkAvailabilityManager,
                 timerSignal: Observable<Unit> = Observable.interval(0L, 3L, TimeUnit.MINUTES).map { Unit }) {

    private val disposables = CompositeDisposable()

    init {
        disposables.add(Observable.merge(
                walletManager.walletsUpdatedSignal,
                currencyManager.baseCurrencyUpdatedSignal,
                networkAvailabilityManager.networkAvailabilitySignal,
                timerSignal)
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .subscribe {
                    requestRefresh()
                })

        requestRefreshZeroRates()

        disposables.add(Observable.merge(
                currencyManager.baseCurrencyUpdatedSignal,
                networkAvailabilityManager.networkAvailabilitySignal)
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .subscribe {
                    requestRefreshZeroRates()
                })
    }

    private fun requestRefreshZeroRates() {
        if (networkAvailabilityManager.isConnected) {
            rateManager.refreshZeroRates(currencyManager.baseCurrency.code)
        }
    }

    private fun requestRefresh() {
        if (networkAvailabilityManager.isConnected) {
            rateManager.refreshLatestRates(walletManager.wallets.map { it.coinCode }, currencyManager.baseCurrency.code)
        }
    }
}

package io.horizontalsystems.bankwallet.core.managers

import android.text.TextUtils
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.core.IEncryptionManager
import io.horizontalsystems.bankwallet.core.ISecuredStorage
import io.horizontalsystems.bankwallet.entities.AuthData

class SecuredStorageManager(private val encryptionManager: IEncryptionManager) : ISecuredStorage {

    private val AUTH_DATA = "auth_data"
    private val LOCK_PIN = "lock_pin"

    override val authData: AuthData?
        get() {
            App.preferences.getString(AUTH_DATA, null)?.let { string ->
                val words = encryptionManager.decrypt(string).split(" ").filter { it.isNotBlank() }

                words.getOrNull(12)?.let { walletId ->
                    return AuthData(words.subList(0, 12), walletId)
                }
            }

            return null
        }

    override fun saveAuthData(authData: AuthData) {
        App.preferences.edit().putString(AUTH_DATA, encryptionManager.encrypt(authData.words.plus(authData.walletId).joinToString(" "))).apply()
    }

    override fun noAuthData(): Boolean {
        val words = App.preferences.getString(AUTH_DATA, null)
        return words.isNullOrEmpty()
    }

    override val savedPin: String?
        get() {
            val string = App.preferences.getString(LOCK_PIN, null)
            return if (TextUtils.isEmpty(string)) {
                null
            } else {
                encryptionManager.decrypt(string)
            }
        }

    override fun savePin(pin: String) {
        App.preferences.edit().putString(LOCK_PIN, encryptionManager.encrypt(pin)).apply()
    }

    override fun pinIsEmpty(): Boolean {
        val string = App.preferences.getString(LOCK_PIN, null)
        return string.isNullOrEmpty()
    }

}

package io.horizontalsystems.bankwallet.core.managers

import android.app.KeyguardManager
import android.content.Context
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat
import io.horizontalsystems.bankwallet.BuildConfig
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.core.ISystemInfoManager
import io.horizontalsystems.bankwallet.entities.BiometryType

class SystemInfoManager: ISystemInfoManager {
    override var appVersion: String = BuildConfig.VERSION_NAME

    override var biometryType: BiometryType = BiometryType.NONE
        get() {
            return when {
                phoneHasFingerprintSensor() -> BiometryType.FINGER
                else -> BiometryType.NONE
            }
        }

    override fun phoneHasFingerprintSensor(): Boolean {
        val fingerprintManager = FingerprintManagerCompat.from(App.instance)
        return fingerprintManager.isHardwareDetected
    }

    override fun touchSensorCanBeUsed(): Boolean {
        val fingerprintManager = FingerprintManagerCompat.from(App.instance)
        return when {
            fingerprintManager.isHardwareDetected -> {
                val keyguardManager = App.instance.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
                keyguardManager.isKeyguardSecure && fingerprintManager.hasEnrolledFingerprints()
            }
            else -> false
        }
    }
}

package io.horizontalsystems.bankwallet.core.managers

import io.horizontalsystems.bankwallet.core.IAppConfigProvider
import io.horizontalsystems.bankwallet.core.ILocalStorage
import io.horizontalsystems.bankwallet.core.ITransactionDataProviderManager
import io.horizontalsystems.bankwallet.modules.fulltransactioninfo.FullTransactionInfoModule.BitcoinForksProvider
import io.horizontalsystems.bankwallet.modules.fulltransactioninfo.FullTransactionInfoModule.EthereumForksProvider
import io.horizontalsystems.bankwallet.modules.fulltransactioninfo.FullTransactionInfoModule.Provider
import io.horizontalsystems.bankwallet.modules.fulltransactioninfo.providers.*
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode
import io.reactivex.subjects.PublishSubject

class TransactionDataProviderManager(private val appConfig: IAppConfigProvider, private val localStorage: ILocalStorage)
    : ITransactionDataProviderManager {

    private val bitcoinProviders
        get() = when {
            appConfig.testMode -> arrayListOf(HorsysBitcoinProvider(testMode = true))
            else -> arrayListOf(
                    HorsysBitcoinProvider(testMode = false),
                    BlockChairBitcoinProvider(),
                    BlockExplorerBitcoinProvider(),
                    BtcComBitcoinProvider())
        }

    private val bitcoinCashProviders
        get() = when {
            appConfig.testMode -> arrayListOf(HorsysBitcoinCashProvider(testMode = true))
            else -> arrayListOf(
                    HorsysBitcoinCashProvider(testMode = false),
                    BlockChairBitcoinCashProvider(),
                    BlockExplorerBitcoinCashProvider(),
                    BtcComBitcoinCashProvider())
        }

    private val ethereumProviders
        get() = when {
            appConfig.testMode -> arrayListOf(HorsysEthereumProvider(testMode = true))
            else -> arrayListOf(
                    HorsysEthereumProvider(testMode = false),
                    EtherscanEthereumProvider(),
                    BlockChairEthereumProvider())
        }

    override val baseProviderUpdatedSignal = PublishSubject.create<Unit>()

    override fun providers(coinCode: CoinCode): List<Provider> {
        return when {
            coinCode.contains("BTC") -> bitcoinProviders
            coinCode.contains("BCH") -> bitcoinCashProviders
            else -> ethereumProviders
        }
    }

    override fun baseProvider(coinCode: CoinCode): Provider {
        if (coinCode.contains("ETH")) {
            return ethereum(localStorage.baseEthereumProvider ?: ethereumProviders[0].name)
        }

        return bitcoin(localStorage.baseBitcoinProvider ?: bitcoinProviders[0].name)
    }

    override fun setBaseProvider(name: String, coinCode: CoinCode) {
        if (coinCode.contains("ETH")) {
            localStorage.baseEthereumProvider = name
        } else {
            localStorage.baseBitcoinProvider = name
        }

        baseProviderUpdatedSignal.onNext(Unit)
    }

    //
    // Providers
    //
    override fun bitcoin(name: String): BitcoinForksProvider {
        bitcoinProviders.let { list ->
            return list.find { it.name == name } ?: list[0]
        }
    }

    override fun bitcoinCash(name: String): BitcoinForksProvider {
        bitcoinCashProviders.let { list ->
            return list.find { it.name == name } ?: list[0]
        }
    }

    override fun ethereum(name: String): EthereumForksProvider {
        ethereumProviders.let { list ->
            return list.find { it.name == name } ?: list[0]
        }
    }
}

package io.horizontalsystems.bankwallet.core.managers

import android.os.SystemClock
import io.horizontalsystems.bankwallet.core.IUptimeProvider

class UptimeProvider: IUptimeProvider {

    override val uptime: Long
        get() = SystemClock.elapsedRealtime()

}

package io.horizontalsystems.bankwallet.core.managers

import android.os.Handler
import android.os.HandlerThread
import io.horizontalsystems.bankwallet.core.IWalletManager
import io.horizontalsystems.bankwallet.core.factories.WalletFactory
import io.horizontalsystems.bankwallet.entities.Wallet
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.PublishSubject

class WalletManager(
        private val coinManager: CoinManager,
        private val authManager: AuthManager,
        private val walletFactory: WalletFactory) : IWalletManager, HandlerThread("A") {

    private val handler: Handler
    private val disposables = CompositeDisposable()

    init {
        start()
        handler = Handler(looper)

        disposables.add(coinManager.coinsUpdatedSignal
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .subscribe {
                    initWallets()
                }
        )

        disposables.add(authManager.authDataSignal
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .subscribe {
                    initWallets()
                }
        )
    }

    override var wallets: List<Wallet> = listOf()
    override val walletsUpdatedSignal = PublishSubject.create<Unit>()

    override fun refreshWallets() {
        handler.post {
            wallets.forEach { it.adapter.refresh() }
        }
    }

    override fun initWallets() {
        authManager.authData?.let { authData ->
            handler.post {
                wallets = coinManager.coins.mapNotNull { coin ->
                    wallets.find { it.coinCode == coin.code }
                            ?: walletFactory.createWallet(coin, authData)
                }

                walletsUpdatedSignal.onNext(Unit)
            }
        }
    }

    override fun clear() {
        handler.post {
            wallets.forEach { it.adapter.clear() }
            wallets = listOf()
            walletsUpdatedSignal.onNext(Unit)
        }
    }
}

package io.horizontalsystems.bankwallet.core.managers

import io.horizontalsystems.bankwallet.core.ILocalStorage
import io.horizontalsystems.bankwallet.core.IWordsManager
import io.horizontalsystems.hdwalletkit.Mnemonic
import io.reactivex.subjects.PublishSubject

class WordsManager(private val localStorage: ILocalStorage) : IWordsManager {

    override var isBackedUp: Boolean
        get() = localStorage.isBackedUp
        set(value) {
            localStorage.isBackedUp = value
            backedUpSignal.onNext(Unit)
        }

    override var backedUpSignal = PublishSubject.create<Unit>()

    @Throws(Mnemonic.MnemonicException::class)
    override fun validate(words: List<String>) {
        Mnemonic().validate(words)
    }

    override fun generateWords() : List<String> {
        return Mnemonic().generate()
    }

}

package io.horizontalsystems.bankwallet.core.security

import android.util.Base64
import java.security.Key
import javax.crypto.Cipher
import javax.crypto.spec.IvParameterSpec

class CipherWrapper {

    companion object {
        const val TRANSFORMATION_SYMMETRIC = "AES/CBC/PKCS7Padding"
        const val IV_SEPARATOR = "]"
    }

    val cipher: Cipher = Cipher.getInstance(TRANSFORMATION_SYMMETRIC)

    fun encrypt(data: String, key: Key?): String {
        cipher.init(Cipher.ENCRYPT_MODE, key)

        val iv = cipher.iv
        val ivString = Base64.encodeToString(iv, Base64.DEFAULT)
        var result = ivString + IV_SEPARATOR

        val bytes = cipher.doFinal(data.toByteArray())
        result += Base64.encodeToString(bytes, Base64.DEFAULT)

        return result
    }

    fun decrypt(data: String, key: Key?): String {
        val split = data.split(IV_SEPARATOR.toRegex())
        if (split.size != 2) throw IllegalArgumentException("Passed data is incorrect. There was no IV specified with it.")

        val ivString = split[0]
        val encodedString = split[1]
        val ivSpec = IvParameterSpec(Base64.decode(ivString, Base64.DEFAULT))
        cipher.init(Cipher.DECRYPT_MODE, key, ivSpec)

        val encryptedData = Base64.decode(encodedString, Base64.DEFAULT)
        val decodedData = cipher.doFinal(encryptedData)
        return String(decodedData)
    }

}

package io.horizontalsystems.bankwallet.core.security

import android.app.Activity
import android.app.KeyguardManager
import android.content.Context
import android.content.Intent
import android.security.keystore.KeyPermanentlyInvalidatedException
import android.support.v4.app.Fragment
import android.support.v4.app.FragmentActivity
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat
import io.horizontalsystems.bankwallet.LauncherActivity
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.core.IEncryptionManager
import io.horizontalsystems.bankwallet.lib.AlertDialogFragment
import javax.crypto.Cipher

class EncryptionManager : IEncryptionManager {

    private val keyStoreWrapper = KeyStoreWrapper()

    @Synchronized
    override fun encrypt(data: String): String {

        var masterKey = keyStoreWrapper.getAndroidKeyStoreSymmetricKey(MASTER_KEY)

        if (masterKey == null) {
            masterKey = keyStoreWrapper.createAndroidKeyStoreSymmetricKey(MASTER_KEY)
        }
        return CipherWrapper().encrypt(data, masterKey)
    }

    @Synchronized
    override fun decrypt(data: String): String {
        val masterKey = keyStoreWrapper.getAndroidKeyStoreSymmetricKey(MASTER_KEY)
                ?: throw KeyPermanentlyInvalidatedException()
        return CipherWrapper().decrypt(data, masterKey)
    }

    override fun getCryptoObject(): FingerprintManagerCompat.CryptoObject {
        var masterKey = keyStoreWrapper.getAndroidKeyStoreSymmetricKey(MASTER_KEY)

        if (masterKey == null) {
            masterKey = keyStoreWrapper.createAndroidKeyStoreSymmetricKey(MASTER_KEY)
        }

        val cipher = CipherWrapper().cipher
        cipher.init(Cipher.ENCRYPT_MODE, masterKey)

        return FingerprintManagerCompat.CryptoObject(cipher)
    }

    companion object {
        const val MASTER_KEY = "MASTER_KEY"

        fun showAuthenticationScreen(activity: Activity, requestCode: Int) {
            val mKeyguardManager = activity.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
            val intent: Intent? = mKeyguardManager.createConfirmDeviceCredentialIntent("Authorization required", "")
            activity.startActivityForResult(intent, requestCode)
        }

        fun showAuthenticationScreen(fragment: Fragment, requestCode: Int) {
            val mKeyguardManager = fragment.activity?.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
            val intent: Intent? = mKeyguardManager.createConfirmDeviceCredentialIntent("Authorization required", "")
            fragment.startActivityForResult(intent, requestCode)
        }

        fun showKeysInvalidatedAlert(activity: FragmentActivity) {
            AlertDialogFragment.newInstance(R.string.Alert_KeysInvalidatedTitle, R.string.Alert_KeysInvalidatedDescription, R.string.Alert_Ok,
                    object : AlertDialogFragment.Listener {
                        override fun onButtonClick() {
                            App.authManager.logout()
                            KeyStoreWrapper().removeAndroidKeyStoreKey(MASTER_KEY)

                            val intent = Intent(activity, LauncherActivity::class.java)
                            intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
                            activity.startActivity(intent)

                            activity.finish()
                        }
                    }).show(activity.supportFragmentManager, "keys_invalidated_alert")
        }

        fun showNoDeviceLockWarning(activity: FragmentActivity) {
            AlertDialogFragment.newInstance(R.string.Alert_TitleWarning, R.string.Alert_NoDeviceLockDescription, R.string.Alert_Close,
                    object : AlertDialogFragment.Listener {
                        override fun onButtonClick() {
                            activity.finish()
                        }
                    }).show(activity.supportFragmentManager, "no_device_lock_alert")
        }

        fun isDeviceLockEnabled(ctx: Context): Boolean {
            val keyguardManager = ctx.getSystemService(Activity.KEYGUARD_SERVICE) as KeyguardManager
            return keyguardManager.isKeyguardSecure
        }

    }

}

package io.horizontalsystems.bankwallet.core.security

/*
 * Copyright (C) 2017 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
 */
//source https://github.com/googlesamples/android-FingerprintDialog

import android.app.DialogFragment
import android.content.DialogInterface
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.hardware.fingerprint.FingerprintManager
import android.os.Bundle
import android.support.v4.content.ContextCompat
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat
import android.support.v4.widget.ImageViewCompat
import android.view.*
import android.view.animation.AnimationUtils
import android.widget.Button
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.TextView
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.viewHelpers.HudHelper

/**
 * A dialog which uses fingerprint APIs to authenticate the user, and falls back to password
 * authentication if fingerprint is not available.
 */
class FingerprintAuthenticationDialogFragment : DialogFragment(), FingerprintCallback {

    private lateinit var cancelButton: Button
    private lateinit var fingerprintWrapper: FrameLayout
    private lateinit var iconBackgroundImg: ImageView
    private lateinit var errorTextView: TextView

    private lateinit var callback: Callback
    private lateinit var cryptoObject: FingerprintManagerCompat.CryptoObject
    private var authCallbackHandler: FingerprintAuthenticationHandler? = null

     private val ERROR_TIMEOUT_MILLIS: Long = 1900
     private val SUCCESS_DELAY_MILLIS: Long = 100

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Do not create a new Fragment when the Activity is re-created such as orientation changes.
        retainInstance = true
        setStyle(DialogFragment.STYLE_NORMAL, android.R.style.Theme_Material_Light_Dialog)
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        dialog.window?.let {
            it.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
            it.requestFeature(Window.FEATURE_NO_TITLE)
        }
        isCancelable = false
        return inflater.inflate(R.layout.fingerprint_dialog_container, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        cancelButton = view.findViewById(R.id.cancel_button)
        fingerprintWrapper = view.findViewById(R.id.fingerprint_wrapper)
        iconBackgroundImg = view.findViewById(R.id.fingerprint_background)
        errorTextView = view.findViewById(R.id.fingerprint_status)

        cancelButton.setOnClickListener { dismiss() }

        authCallbackHandler = FingerprintAuthenticationHandler(this, cryptoObject)
    }

    override fun onPause() {
        super.onPause()
        authCallbackHandler?.stopListening()
    }

    override fun onResume() {
        super.onResume()
        authCallbackHandler?.startListening()

        dialog.setOnKeyListener { _, keyCode, _ ->

            if ((keyCode == KeyEvent.KEYCODE_BACK)) {
                // To dismiss the fragment when the back-button is pressed.
                dismiss()
                true
            }
            // Otherwise, do nothing else
            else false
        }
    }

    override fun onDismiss(dialog: DialogInterface?) {
        super.onDismiss(dialog)
        authCallbackHandler?.releaseFingerprintCallback()
    }

    override fun onAuthenticated() {
        errorTextView.run {
            removeCallbacks(resetErrorTextRunnable)
            setTextColor(errorTextView.resources.getColor(R.color.green_crypto, null))
            text = errorTextView.resources.getString(R.string.Fingerprint_Success)
        }
        iconBackgroundImg.run {
            setImageTintColor(iconBackgroundImg, R.color.green_crypto)
            postDelayed({ callback.onFingerprintAuthSucceed() }, SUCCESS_DELAY_MILLIS)
        }
    }

    override fun onAuthenticationHelp(helpString: CharSequence?) { }

    override fun onAuthenticationFailed() {
        showError(getString(R.string.Fingerprint_NotRecognized))
    }

    override fun onAuthenticationError(errMsgId: Int, errString: CharSequence?) {
        if (errMsgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT) {
            HudHelper.showErrorMessage(R.string.Unlock_Page_EnterYourPin)
            dismiss()
        }
    }

    fun setCallback(callback: Callback) {
        this.callback = callback
    }

    fun setCryptoObject(cryptoObject: FingerprintManagerCompat.CryptoObject) {
        this.cryptoObject = cryptoObject
    }

    private val resetErrorTextRunnable = Runnable {
        setImageTintColor(iconBackgroundImg, R.color.dark)
        errorTextView.run {
            setTextColor(errorTextView.resources.getColor(R.color.dark, null))
            text = errorTextView.resources.getString(R.string.Fingerprint_Hint)
        }
    }

    private fun showError(error: CharSequence) {
        dialog?.let {
            errorTextView.run {
                text = error
                setTextColor(errorTextView.resources.getColor(R.color.red_warning, null))
                removeCallbacks(resetErrorTextRunnable)
                postDelayed(resetErrorTextRunnable, ERROR_TIMEOUT_MILLIS)
            }

            setImageTintColor(iconBackgroundImg, R.color.red_warning)
            val shake = AnimationUtils.loadAnimation(it.context, R.anim.shake)
            fingerprintWrapper.startAnimation(shake)
        }
    }

    private fun setImageTintColor(image: ImageView, colorResource: Int) {
        val color = ContextCompat.getColor(image.context, colorResource)
        ImageViewCompat.setImageTintList(image, ColorStateList.valueOf(color))
    }

    interface Callback {
        fun onFingerprintAuthSucceed()
    }
}

package io.horizontalsystems.bankwallet.core.security

import android.support.v4.hardware.fingerprint.FingerprintManagerCompat
import android.support.v4.os.CancellationSignal
import io.horizontalsystems.bankwallet.core.App

class FingerprintAuthenticationHandler(
        private var callback: FingerprintCallback?,
        private val cryptoObject: FingerprintManagerCompat.CryptoObject) : FingerprintManagerCompat.AuthenticationCallback() {

    private val fingerprintManager: FingerprintManagerCompat = FingerprintManagerCompat.from(App.instance)
    private var cancellationSignal: CancellationSignal? = null

    fun releaseFingerprintCallback() {
        callback = null
    }

    /**
     * Start listening for fingerprint authentication.
     */
    fun startListening() {
        cancellationSignal = CancellationSignal()
        fingerprintManager.authenticate(cryptoObject, 0, cancellationSignal, this, null)
    }

    /**
     * Stop listening for fingerprint authentication.
     */
    fun stopListening() {
        cancellationSignal?.cancel()
        cancellationSignal = null
    }

    override fun onAuthenticationError(errMsgId: Int, errString: CharSequence?) {
        super.onAuthenticationError(errMsgId, errString)
        callback?.onAuthenticationError(errMsgId, errString)
    }

    override fun onAuthenticationSucceeded(result: FingerprintManagerCompat.AuthenticationResult?) {
        super.onAuthenticationSucceeded(result)
        callback?.onAuthenticated()
    }

    override fun onAuthenticationHelp(helpMsgId: Int, helpString: CharSequence?) {
        super.onAuthenticationHelp(helpMsgId, helpString)
        callback?.onAuthenticationHelp(helpString)
    }

    override fun onAuthenticationFailed() {
        super.onAuthenticationFailed()
        callback?.onAuthenticationFailed()
    }
}

package io.horizontalsystems.bankwallet.core.security

interface FingerprintCallback {
    fun onAuthenticated()

    fun onAuthenticationHelp(helpString: CharSequence?)

    fun onAuthenticationFailed()

    fun onAuthenticationError(errMsgId: Int, errString: CharSequence?)
}

package io.horizontalsystems.bankwallet.core.security

import android.security.keystore.KeyGenParameterSpec
import android.security.keystore.KeyProperties
import android.util.Log
import java.security.KeyStore
import java.security.KeyStoreException
import javax.crypto.KeyGenerator
import javax.crypto.SecretKey

class KeyStoreWrapper {

    companion object {
        private const val ANDROID_KEY_STORE = "AndroidKeyStore"

        private const val BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC
        private const val PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7

        private const val AUTH_DURATION_SEC = 1800 //30 minute
    }

    private val keyStore: KeyStore = getAndroidKeyStore()

    private fun getAndroidKeyStore(): KeyStore {
        val keyStore = KeyStore.getInstance(ANDROID_KEY_STORE)
        keyStore.load(null)
        return keyStore
    }

    fun createAndroidKeyStoreSymmetricKey(alias: String): SecretKey {

        val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE)

        keyGenerator.init(KeyGenParameterSpec.Builder(alias,
                KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
                .setBlockModes(BLOCK_MODE)
                .setUserAuthenticationRequired(true)
                .setUserAuthenticationValidityDurationSeconds(AUTH_DURATION_SEC)
                .setRandomizedEncryptionRequired(false)
                .setEncryptionPaddings(PADDING)
                .build())

        return keyGenerator.generateKey()
    }

    fun getAndroidKeyStoreSymmetricKey(alias: String): SecretKey? = try {
        keyStore.getKey(alias, null) as? SecretKey
    } catch (e: Exception) {
        null
    }

    fun removeAndroidKeyStoreKey(alias: String) {
        try {
            keyStore.deleteEntry(alias)
        } catch (e: KeyStoreException) {
            Log.e("KeyStoreWrapper", "Exception", e)
        }
    }
}

package io.horizontalsystems.bankwallet.core.storage

import android.arch.persistence.db.SupportSQLiteDatabase
import android.arch.persistence.room.Database
import android.arch.persistence.room.Room
import android.arch.persistence.room.RoomDatabase
import android.arch.persistence.room.migration.Migration
import android.content.ContentValues
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import io.horizontalsystems.bankwallet.BuildConfig
import io.horizontalsystems.bankwallet.entities.Rate
import io.horizontalsystems.bankwallet.entities.StorableCoin

@Database(entities = [Rate::class, StorableCoin::class], version = 5, exportSchema = true)
abstract class AppDatabase : RoomDatabase() {

    abstract fun ratesDao(): RatesDao

    abstract fun coinsDao(): StorableCoinsDao

    companion object {

        @Volatile private var INSTANCE: AppDatabase? = null

        fun getInstance(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                INSTANCE ?: buildDatabase(context).also { INSTANCE = it }
            }
        }

        private fun buildDatabase(context: Context): AppDatabase {
            return Room.databaseBuilder(context, AppDatabase::class.java, "dbBankWallet")
                    .fallbackToDestructiveMigration()
                    .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5)
                    .build()
        }

        private val MIGRATION_1_2: Migration = object : Migration(1, 2) {
            override fun migrate(database: SupportSQLiteDatabase) {
                //renaming column coin to coinCode
                database.execSQL("ALTER TABLE TransactionRecord RENAME TO TransactionTemp")
                database.execSQL("CREATE TABLE TransactionRecord (`transactionHash` TEXT NOT NULL PRIMARY KEY,`blockHeight` INTEGER NOT NULL,`coinCode` TEXT NOT NULL,`amount` REAL NOT NULL,`timestamp` INTEGER NOT NULL,`rate` REAL NOT NULL,`from` TEXT NOT NULL,`to` TEXT NOT NULL)")
                database.execSQL("INSERT INTO TransactionRecord (`transactionHash`,`blockHeight`,`coinCode`,`amount`,`timestamp`,`rate`,`from`,`to`) SELECT `transactionHash`,`blockHeight`,`coin`,`amount`,`timestamp`,`rate`,`from`,`to` FROM TransactionTemp")
                database.execSQL("DROP TABLE TransactionTemp")

                database.execSQL("ALTER TABLE Rate RENAME TO RateTemp")
                database.execSQL("CREATE TABLE Rate (`coinCode` TEXT NOT NULL,`currencyCode` TEXT NOT NULL,`value` REAL NOT NULL,`timestamp` INTEGER NOT NULL, PRIMARY KEY(`coinCode`,`currencyCode`))")
                database.execSQL("INSERT INTO Rate (`coinCode`,`currencyCode`,`value`,`timestamp`) SELECT `coin`,`currencyCode`,`value`,`timestamp` FROM RateTemp")
                database.execSQL("DROP TABLE RateTemp")
            }
        }

        private val MIGRATION_2_3: Migration = object : Migration(2, 3) {
            override fun migrate(database: SupportSQLiteDatabase) {
                //create new table Coin
                database.execSQL("CREATE TABLE IF NOT EXISTS StorableCoin (`coinTitle` TEXT NOT NULL, `coinCode` TEXT NOT NULL, `coinType` TEXT NOT NULL, `enabled` INTEGER NOT NULL, `order` INTEGER, PRIMARY KEY(`coinCode`))")
                //save default coin
                val suffix = if (BuildConfig.testMode) "t" else ""

                val bitcoinValues = ContentValues()
                bitcoinValues.put("coinCode", "BTC$suffix")
                bitcoinValues.put("coinTitle", "Bitcoin")
                bitcoinValues.put("coinType", "bitcoin_key")
                bitcoinValues.put("enabled", true)
                bitcoinValues.put("`order`", 0)

                val bitcoinCashValues = ContentValues()
                bitcoinCashValues.put("coinCode", "BCH$suffix")
                bitcoinCashValues.put("coinTitle", "Bitcoin Cash")
                bitcoinCashValues.put("coinType", "bitcoin_cash_key")
                bitcoinCashValues.put("enabled", true)
                bitcoinCashValues.put("`order`", 1)

                val ethereumValues = ContentValues()
                ethereumValues.put("coinCode", "ETH$suffix")
                ethereumValues.put("coinTitle", "Ethereum")
                ethereumValues.put("coinType", "ethereum_key")
                ethereumValues.put("enabled", true)
                ethereumValues.put("`order`", 2)

                database.insert("StorableCoin", SQLiteDatabase.CONFLICT_REPLACE, bitcoinValues)
                database.insert("StorableCoin", SQLiteDatabase.CONFLICT_REPLACE, bitcoinCashValues)
                database.insert("StorableCoin", SQLiteDatabase.CONFLICT_REPLACE, ethereumValues)
            }
        }

        private val MIGRATION_3_4: Migration = object : Migration(3, 4) {
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("DROP TABLE IF EXISTS TransactionRecord")
                database.execSQL("DROP TABLE IF EXISTS Rate")
                database.execSQL("CREATE TABLE Rate (`coinCode` TEXT NOT NULL,`currencyCode` TEXT NOT NULL,`value` REAL NOT NULL,`timestamp` INTEGER NOT NULL, `isLatest` INTEGER NOT NULL, PRIMARY KEY(`coinCode`,`currencyCode`,`timestamp`,`isLatest`))")
            }
        }

        private val MIGRATION_4_5: Migration = object : Migration(4, 5) {
            override fun migrate(database: SupportSQLiteDatabase) {
                database.execSQL("DROP TABLE IF EXISTS Rate")
                database.execSQL("CREATE TABLE Rate (`coinCode` TEXT NOT NULL,`currencyCode` TEXT NOT NULL,`value` TEXT NOT NULL,`timestamp` INTEGER NOT NULL, `isLatest` INTEGER NOT NULL, PRIMARY KEY(`coinCode`,`currencyCode`,`timestamp`,`isLatest`))")
            }
        }

    }

}

package io.horizontalsystems.bankwallet.core.storage

import android.arch.persistence.room.TypeConverter
import io.horizontalsystems.bankwallet.core.Error
import io.horizontalsystems.bankwallet.entities.CoinType

class CoinTypeConverter {

    private val bitcoinKey = "bitcoin_key"
    private val bitcoinCashKey = "bitcoin_cash_key"
    private val ethereumKey = "ethereum_key"
    private val erc20Key = "erc_20_key"

    @TypeConverter
    fun stringToCoinType(value: String): CoinType {
        return when (value) {
            bitcoinKey -> CoinType.Bitcoin
            bitcoinCashKey -> CoinType.BitcoinCash
            ethereumKey -> CoinType.Ethereum
            else -> {
                if (value.contains(erc20Key)) {
                    try {
                        val parts = value.split(";")
                        if (parts.size == 3) {
                            return CoinType.Erc20(parts[1], parts[2].toInt())
                        }
                    } catch (e: Exception) { }
                }
                throw Error.CoinTypeException()
            }
        }
    }

    @TypeConverter
    fun coinTypeToString(value: CoinType): String {
        return when (value) {
            is CoinType.Bitcoin -> bitcoinKey
            is CoinType.BitcoinCash -> bitcoinCashKey
            is CoinType.Ethereum -> ethereumKey
            is CoinType.Erc20 -> "$erc20Key;${value.address};${value.decimal}"
        }
    }
}

package io.horizontalsystems.bankwallet.core.storage

import android.arch.persistence.room.TypeConverter
import java.math.BigDecimal

class DatabaseConverters {

    @TypeConverter
    fun fromString(value: String?): BigDecimal? {
        return value?.let { BigDecimal(it) } ?: null
    }

    @TypeConverter
    fun toString(bigDecimal: BigDecimal?): String? {
        return bigDecimal?.let {
            it.toPlainString()
        }
    }
}

package io.horizontalsystems.bankwallet.core.storage

import android.arch.persistence.room.*
import io.horizontalsystems.bankwallet.entities.Rate
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode
import io.reactivex.Flowable
import io.reactivex.Single

@Dao
interface RatesDao {

    @Query("SELECT * FROM Rate WHERE coinCode = :coinCode AND currencyCode = :currencyCode AND isLatest = 1")
    fun getLatestRate(coinCode: CoinCode, currencyCode: String): Flowable<Rate>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(rate: Rate)

    @Delete
    fun delete(rate: Rate)

    @Query("DELETE FROM Rate WHERE isLatest = 1 AND coinCode = :coinCode AND currencyCode = :currencyCode")
    fun deleteLatest(coinCode: CoinCode, currencyCode: String)

    @Query("DELETE FROM Rate")
    fun deleteAll()

    @Query("SELECT * FROM Rate WHERE coinCode = :coinCode AND currencyCode = :currencyCode AND timestamp = :timestamp AND isLatest = 0")
    fun getRate(coinCode: CoinCode, currencyCode: String, timestamp: Long): Flowable<List<Rate>>

    @Query("SELECT * FROM Rate WHERE value = 0.0 AND currencyCode = :currencyCode AND isLatest = 0")
    fun getZeroRates(currencyCode: String): Single<List<Rate>>

}

package io.horizontalsystems.bankwallet.core.storage

import io.horizontalsystems.bankwallet.core.IRateStorage
import io.horizontalsystems.bankwallet.entities.Rate
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode
import io.reactivex.Flowable
import io.reactivex.Single
import java.util.concurrent.Executors

class RatesRepository(private val appDatabase: AppDatabase) : IRateStorage {

    private val executor = Executors.newSingleThreadExecutor()

    override fun latestRateObservable(coinCode: CoinCode, currencyCode: String): Flowable<Rate> {
        return appDatabase.ratesDao().getLatestRate(coinCode, currencyCode).distinctUntilChanged()
    }

    override fun rateObservable(coinCode: CoinCode, currencyCode: String, timestamp: Long): Flowable<List<Rate>> {
        return appDatabase.ratesDao().getRate(coinCode, currencyCode, timestamp)
    }

    override fun save(rate: Rate) {
        executor.execute {
            appDatabase.ratesDao().insert(rate)
        }
    }

    override fun saveLatest(rate: Rate) {
        executor.execute {
            appDatabase.ratesDao().deleteLatest(rate.coinCode, rate.currencyCode)
            appDatabase.ratesDao().insert(rate)
        }
    }

    override fun deleteAll() {
        executor.execute {
            appDatabase.ratesDao().deleteAll()
        }
    }

    override fun zeroRatesObservable(currencyCode: String): Single<List<Rate>> {
        return appDatabase.ratesDao().getZeroRates(currencyCode)
    }
}

package io.horizontalsystems.bankwallet.core.storage

import android.arch.persistence.room.Dao
import android.arch.persistence.room.Insert
import android.arch.persistence.room.OnConflictStrategy
import android.arch.persistence.room.Query
import io.horizontalsystems.bankwallet.entities.StorableCoin
import io.reactivex.Flowable

@Dao
interface StorableCoinsDao {

    @Query("SELECT * FROM StorableCoin WHERE coinCode = :coinCode")
    fun getCoin(coinCode: String): Flowable<StorableCoin>

    @Query("SELECT * FROM StorableCoin ORDER BY `coinTitle` ASC")
    fun getAllCoins(): Flowable<List<StorableCoin>>

    @Query("SELECT * FROM StorableCoin WHERE enabled = 1 ORDER BY `order` ASC")
    fun getEnabledCoin(): Flowable<List<StorableCoin>>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(storableCoin: StorableCoin)

    @Query("DELETE FROM StorableCoin")
    fun deleteAll()

    @Query("UPDATE StorableCoin SET enabled = 0, `order` = null")
    fun resetCoinsState()

}

package io.horizontalsystems.bankwallet.core.storage

import io.horizontalsystems.bankwallet.core.ICoinStorage
import io.horizontalsystems.bankwallet.entities.Coin
import io.horizontalsystems.bankwallet.entities.StorableCoin
import io.reactivex.Flowable
import java.util.concurrent.Executors

class StorableCoinsRepository(private val appDatabase: AppDatabase) : ICoinStorage {

    private val executor = Executors.newSingleThreadExecutor()

    override fun enabledCoinsObservable(): Flowable<List<Coin>> {
        return appDatabase.coinsDao().getEnabledCoin()
                .flatMap { list ->
                    Flowable.just(list.map { Coin(it.coinTitle, it.coinCode, it.coinType) })
                }
    }

    override fun allCoinsObservable(): Flowable<List<Coin>> {
        return appDatabase.coinsDao().getAllCoins()
                .flatMap { list ->
                    Flowable.just(list.map { Coin(it.coinTitle, it.coinCode, it.coinType) })
                }
    }

    override fun save(coins: List<Coin>) {

        executor.execute {
            //update stored coins enabled as false, and order as null
            appDatabase.coinsDao().resetCoinsState()

            //save coin list
            coins.forEachIndexed { index, coin ->
                val storableCoin = StorableCoin(
                        coinCode = coin.code,
                        coinTitle = coin.title,
                        coinType = coin.type,
                        enabled = true,
                        order = index)
                appDatabase.coinsDao().insert(storableCoin)
            }
        }
    }

    override fun deleteAll() {
        executor.execute {
            appDatabase.coinsDao().deleteAll()
        }
    }

}

package io.horizontalsystems.bankwallet.modules.backup

import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.content.Context
import android.content.Intent
import android.os.Bundle
import io.horizontalsystems.bankwallet.BaseActivity
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.core.setOnSingleClickListener
import io.horizontalsystems.bankwallet.modules.pin.PinModule
import io.horizontalsystems.bankwallet.ui.dialogs.BottomConfirmAlert
import kotlinx.android.synthetic.main.activity_backup_words.*

class BackupActivity : BaseActivity(), BottomConfirmAlert.Listener {

    private lateinit var viewModel: BackupViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_backup_words)

        viewModel = ViewModelProviders.of(this).get(BackupViewModel::class.java)
        viewModel.init(BackupPresenter.DismissMode.valueOf(intent.getStringExtra(dismissModeKey)))

        if (savedInstanceState == null) {
            viewModel.delegate.viewDidLoad()
        }

        buttonBack.setOnSingleClickListener { viewModel.delegate.onBackClick() }
        buttonNext.setOnSingleClickListener { viewModel.delegate.onNextClick() }

        viewModel.navigateToSetPinLiveEvent.observe(this, Observer {
            PinModule.startForSetPin(this)
        })

        viewModel.closeLiveEvent.observe(this, Observer {
            finish()
        })

        viewModel.keyStoreSafeExecute.observe(this, Observer { triple ->
            triple?.let {
                val (action, onSuccess, onFailure) = it
                safeExecuteWithKeystore(action, onSuccess, onFailure)
            }
        })

        viewModel.showConfirmationCheckDialogLiveEvent.observe(this, Observer {
            val confirmationList = mutableListOf(
                    R.string.Backup_Confirmation_Understand,
                    R.string.Backup_Confirmation_DeleteAppWarn,
                    R.string.Backup_Confirmation_LockAppWarn
            )
            BottomConfirmAlert.show(this, confirmationList, this)
        })

        viewModel.loadPageLiveEvent.observe(this, Observer { page ->
            page?.let {
                val fragment = when(it) {
                    0 -> BackupInfoFragment()
                    1 -> BackupWordsFragment()
                    else -> BackupConfirmFragment()
                }
                val transaction = supportFragmentManager.beginTransaction()
                transaction.replace(R.id.fragmentContainer, fragment)
                transaction.addToBackStack(null)
                transaction.commit()

                setButtons(it)
            }
        })

    }

    private fun setButtons(page: Int) {
        when(page) {
            0 -> {
                buttonBack.setText(R.string.Backup_Intro_Later)
                buttonNext.setText(R.string.Backup_Intro_BackupNow)
            }
            1 -> {
                buttonBack.setText(R.string.Button_Back)
                buttonNext.setText(R.string.Backup_Button_Next)
            }
            else -> {
                buttonBack.setText(R.string.Button_Back)
                buttonNext.setText(R.string.Backup_Button_Submit)
            }
        }
    }

    override fun onBackPressed() {
        viewModel.delegate.onBackClick()
    }

    override fun onConfirmationSuccess() {
        viewModel.delegate.onTermsConfirm()
    }

    companion object {
        private const val dismissModeKey = "DismissMode"

        fun start(context: Context, dismissMode: BackupPresenter.DismissMode) {
            val intent = Intent(context, BackupActivity::class.java)
            intent.putExtra(dismissModeKey, dismissMode.name)
            context.startActivity(intent)
        }
    }
}

package io.horizontalsystems.bankwallet.modules.backup

import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import io.horizontalsystems.bankwallet.BaseActivity
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.core.Utils
import io.horizontalsystems.bankwallet.viewHelpers.HudHelper
import kotlinx.android.synthetic.main.fragment_backup_words_confirm.*

class BackupConfirmFragment : Fragment() {
    private lateinit var viewModel: BackupViewModel

    private var wordIndex1 = -1
    private var wordIndex2 = -1

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_backup_words_confirm, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        activity?.let {
            viewModel = ViewModelProviders.of(it).get(BackupViewModel::class.java)
        }

        context?.let {
            if (Utils.isUsingCustomKeyboard(it) ) {
                (activity as? BaseActivity)?.showCustomKeyboardAlert()
            }
        }

        viewModel.wordIndexesToConfirmLiveData.observe(this, Observer { list ->
            list?.let {
                textWordNumber1.text = "${it[0]}."
                textWordNumber2.text = "${it[1]}."

                wordIndex1 = it[0]
                wordIndex2 = it[1]
            }
        })

        viewModel.errorLiveData.observe(this, Observer {
            showError(it)
        })

        viewModel.validateWordsLiveEvent.observe(this, Observer {
            if (editWord1.text?.isEmpty() == true || editWord2.text?.isEmpty() == true) {
                showError(R.string.Backup_Confirmation_Description)
            } else {
                viewModel.delegate.validateDidClick(
                        hashMapOf(wordIndex1 to editWord1.text.toString(),
                                wordIndex2 to editWord2.text.toString())
                )
            }
        })
    }

    private fun showError(errorMsgId: Int?) {
        errorMsgId?.let { HudHelper.showErrorMessage(it) }
    }

}

package io.horizontalsystems.bankwallet.modules.backup

import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import io.horizontalsystems.bankwallet.R

class BackupInfoFragment : Fragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_backup_words_info, container, false)
    }

}

package io.horizontalsystems.bankwallet.modules.backup

import io.horizontalsystems.bankwallet.core.IKeyStoreSafeExecute
import io.horizontalsystems.bankwallet.core.ILocalStorage
import io.horizontalsystems.bankwallet.core.IRandomProvider
import io.horizontalsystems.bankwallet.core.managers.AuthManager
import io.horizontalsystems.bankwallet.core.managers.WordsManager
import java.util.*

class BackupInteractor(
        private val authManager: AuthManager,
        private val wordsManager: WordsManager,
        private val indexesProvider: IRandomProvider,
        private val localStorage: ILocalStorage,
        private val keystoreSafeExecute: IKeyStoreSafeExecute) : BackupModule.IInteractor {

    var delegate: BackupModule.IInteractorDelegate? = null

    override fun fetchWords() {
        authManager.authData?.let {
            delegate?.didFetchWords(it.words)
        } ?:run {
            keystoreSafeExecute.safeExecute(
                    action = Runnable {
                        authManager.safeLoad()
                        authManager.authData?.let { delegate?.didFetchWords(it.words) }
                    }
            )
        }
    }

    override fun fetchConfirmationIndexes() {
        delegate?.didFetchConfirmationIndexes(indexesProvider.getRandomIndexes(2))
    }

    override fun shouldShowTermsConfirmation(): Boolean {
        return !localStorage.iUnderstand
    }

    override fun validate(confirmationWords: HashMap<Int, String>) {
        authManager.authData?.let { authData ->
            val wordList = authData.words
            var valid = true
            for ((index, word) in confirmationWords) {
                if (wordList[index - 1] != word.trim()) {
                    valid = false
                }
            }

            if (valid) {
                wordsManager.isBackedUp = true
                delegate?.didValidateSuccess()
            } else {
                delegate?.didValidateFailure()
            }
        } ?: run { delegate?.didValidateFailure() }
    }

    override fun onTermsConfirm() {
        localStorage.iUnderstand = true
    }
}

package io.horizontalsystems.bankwallet.modules.backup

import android.content.Context
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.core.IKeyStoreSafeExecute
import java.util.*

object BackupModule {

    interface IView {
        fun showWords(words: List<String>)
        fun showConfirmationWords(indexes: List<Int>)
        fun showConfirmationError()
        fun showTermsConfirmDialog()

        fun loadPage(page: Int)
        fun validateWords()
    }

    interface IViewDelegate {
        fun viewDidLoad()
        fun onNextClick()
        fun onBackClick()
        fun validateDidClick(confirmationWords: HashMap<Int, String>)
        fun onTermsConfirm()
    }

    interface IInteractor {
        fun fetchWords()
        fun fetchConfirmationIndexes()
        fun validate(confirmationWords: HashMap<Int, String>)
        fun onTermsConfirm()
        fun shouldShowTermsConfirmation(): Boolean
    }

    interface IInteractorDelegate {
        fun didFetchWords(words: List<String>)
        fun didFetchConfirmationIndexes(indexes: List<Int>)
        fun didValidateSuccess()
        fun didValidateFailure()
    }

    interface IRouter {
        fun navigateToSetPin()
        fun close()
    }

    // helpers

    fun start(context: Context, dismissMode: BackupPresenter.DismissMode) {
        BackupActivity.start(context, dismissMode)
    }

    fun init(view: BackupViewModel, router: IRouter, keystoreSafeExecute: IKeyStoreSafeExecute, dismissMode: BackupPresenter.DismissMode) {
        val interactor = BackupInteractor(App.authManager, App.wordsManager, App.randomManager, App.localStorage, keystoreSafeExecute)
        val presenter = BackupPresenter(interactor, router, dismissMode, BackupModuleState())

        presenter.view = view

        interactor.delegate = presenter

        view.delegate = presenter
    }

    class BackupModuleState {
        private val pagesCount: Int = 3
        var currentPage: Int = 0

        fun canLoadNextPage(): Boolean {
            if ((currentPage + 1) < pagesCount) {
                currentPage++
                return true
            }
            return false
        }
        fun canLoadPrevPage(): Boolean{
            if (currentPage > 0) {
                currentPage--
                return true
            }
            return false
        }
    }

}

package io.horizontalsystems.bankwallet.modules.backup

import java.util.*

class BackupPresenter(
        private val interactor: BackupModule.IInteractor,
        private val router: BackupModule.IRouter,
        private val dismissMode: DismissMode,
        private val state: BackupModule.BackupModuleState) : BackupModule.IViewDelegate, BackupModule.IInteractorDelegate {

    enum class DismissMode {
        SET_PIN, DISMISS_SELF
    }

    var view: BackupModule.IView? = null

    // view delegate

    override fun viewDidLoad() {
        loadPage()
    }

    override fun onNextClick() {
        if (state.canLoadNextPage()) {
            when {
                state.currentPage == 1 -> interactor.fetchWords()
                state.currentPage == 2 -> interactor.fetchConfirmationIndexes()
            }
            loadPage()
        } else {
            view?.validateWords()
        }
    }

    override fun onBackClick() {
        if (state.canLoadPrevPage()) {
            loadPage()
        } else {
            dismissOrShowConfirmationDialog()
        }
    }

    override fun validateDidClick(confirmationWords: HashMap<Int, String>) {
        interactor.validate(confirmationWords)
    }

    override fun onTermsConfirm() {
        interactor.onTermsConfirm()
        dismiss()
    }

    // interactor delegate

    override fun didFetchWords(words: List<String>) {
        view?.showWords(words)
    }

    override fun didFetchConfirmationIndexes(indexes: List<Int>) {
        view?.showConfirmationWords(indexes)
    }

    override fun didValidateSuccess() {
        dismissOrShowConfirmationDialog()
    }

    override fun didValidateFailure() {
        view?.showConfirmationError()
    }

    private fun dismissOrShowConfirmationDialog() {
        if (interactor.shouldShowTermsConfirmation()) {
            view?.showTermsConfirmDialog()
        } else {
            dismiss()
        }
    }

    private fun loadPage() {
        view?.loadPage(state.currentPage)
    }

    private fun dismiss() = when (dismissMode) {
        DismissMode.SET_PIN -> router.navigateToSetPin()
        DismissMode.DISMISS_SELF -> router.close()
    }

}

package io.horizontalsystems.bankwallet.modules.backup

import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.SingleLiveEvent
import io.horizontalsystems.bankwallet.core.IKeyStoreSafeExecute

class BackupViewModel : ViewModel(), BackupModule.IView, BackupModule.IRouter, IKeyStoreSafeExecute {
    lateinit var delegate: BackupModule.IViewDelegate

    val loadPageLiveEvent = SingleLiveEvent<Int>()
    val errorLiveData = SingleLiveEvent<Int>()
    val wordsLiveData = MutableLiveData<List<String>>()
    val wordIndexesToConfirmLiveData = MutableLiveData<List<Int>>()
    val validateWordsLiveEvent = SingleLiveEvent<Void>()
    val closeLiveEvent = SingleLiveEvent<Void>()
    val navigateToSetPinLiveEvent = SingleLiveEvent<Void>()
    val showConfirmationCheckDialogLiveEvent = SingleLiveEvent<Void>()
    val keyStoreSafeExecute = SingleLiveEvent<Triple<Runnable, Runnable?, Runnable?>>()

    fun init(dismissMode: BackupPresenter.DismissMode) {
        BackupModule.init(this, this, this, dismissMode)
    }

    // view

    override fun loadPage(page: Int) {
        loadPageLiveEvent.value = page
    }

    override fun showWords(words: List<String>) {
        wordsLiveData.value = words
    }

    override fun showConfirmationWords(indexes: List<Int>) {
        wordIndexesToConfirmLiveData.value = indexes
    }

    override fun showConfirmationError() {
        errorLiveData.value = R.string.Backup_Confirmation_FailureAlertText
    }

    override fun showTermsConfirmDialog() {
        showConfirmationCheckDialogLiveEvent.call()
    }

    override fun validateWords() {
        validateWordsLiveEvent.call()
    }

    // router

    override fun close() {
        closeLiveEvent.call()
    }

    override fun navigateToSetPin() {
        navigateToSetPinLiveEvent.call()
    }

    override fun safeExecute(action: Runnable, onSuccess: Runnable?, onFailure: Runnable?) {
        keyStoreSafeExecute.value = Triple(action, onSuccess, onFailure)
    }
}

package io.horizontalsystems.bankwallet.modules.backup

import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v4.app.Fragment
import android.support.v7.widget.LinearLayoutManager
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.lib.WordsAdapter
import kotlinx.android.synthetic.main.fragment_backup_words_show_words.*

class BackupWordsFragment : Fragment() {

    private lateinit var viewModel: BackupViewModel

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_backup_words_show_words, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val wordsAdapter = WordsAdapter()

        activity?.let {
            viewModel = ViewModelProviders.of(it).get(BackupViewModel::class.java)
        }

        recyclerWords.adapter = wordsAdapter
        recyclerWords.layoutManager = LinearLayoutManager(context)

        viewModel.wordsLiveData.observe(this, Observer {
            it?.let {
                wordsAdapter.items = it
                wordsAdapter.notifyDataSetChanged()
            }
        })
    }

}

package io.horizontalsystems.bankwallet.modules.balance

import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v4.content.ContextCompat
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.SimpleItemAnimator
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.core.AdapterState
import io.horizontalsystems.bankwallet.core.setOnSingleClickListener
import io.horizontalsystems.bankwallet.modules.managecoins.ManageCoinsModule
import io.horizontalsystems.bankwallet.modules.receive.ReceiveModule
import io.horizontalsystems.bankwallet.modules.send.SendModule
import io.horizontalsystems.bankwallet.viewHelpers.AnimationHelper
import io.horizontalsystems.bankwallet.viewHelpers.LayoutHelper
import io.horizontalsystems.bankwallet.viewHelpers.TextHelper
import io.horizontalsystems.bankwallet.viewHelpers.ValueFormatter
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.fragment_wallet.*
import kotlinx.android.synthetic.main.view_holder_coin.*
import java.math.BigDecimal

class BalanceFragment : android.support.v4.app.Fragment(), CoinsAdapter.Listener {

    private lateinit var viewModel: BalanceViewModel
    private var coinsAdapter = CoinsAdapter(this)

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_wallet, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        toolbar.setTitle(R.string.Balance_Title)

        viewModel = ViewModelProviders.of(this).get(BalanceViewModel::class.java)
        viewModel.init()

        viewModel.totalBalanceLiveData.observe(this, Observer { total ->
            ballanceText.text = total?.let { ValueFormatter.format(it) } ?: ""
        })

        viewModel.openReceiveDialog.observe(this, Observer { adapterId ->
            adapterId?.let { id ->
                activity?.let {
                    ReceiveModule.start(it, id)
                }
            }
        })

        viewModel.openSendDialog.observe(this, Observer { iAdapter ->
            iAdapter?.let { coin ->
                activity?.let {
                    SendModule.start(it, coin)
                }
            }
        })

        viewModel.balanceColorLiveDate.observe(this, Observer { color ->
            color?.let { colorRes ->
                context?.let { it ->
                    ballanceText.setTextColor(ContextCompat.getColor(it, colorRes))
                }
            }
        })

        viewModel.didRefreshLiveEvent.observe(this, Observer {
            pullToRefresh.isRefreshing = false
        })

        viewModel.openManageCoinsLiveEvent.observe(this, Observer {
            context?.let { context -> ManageCoinsModule.start(context) }
        })

        viewModel.reloadLiveEvent.observe(this, Observer {
            coinsAdapter.notifyDataSetChanged()
            reloadHeader()
        })

        viewModel.reloadHeaderLiveEvent.observe(this, Observer {
            reloadHeader()
        })

        viewModel.reloadItemLiveEvent.observe(this, Observer { position ->
            position?.let {
                coinsAdapter.notifyItemChanged(it)
            }
        })

        coinsAdapter.viewDelegate = viewModel.delegate
        recyclerCoins.adapter = coinsAdapter
        recyclerCoins.layoutManager = LinearLayoutManager(context)
        (recyclerCoins.itemAnimator as? SimpleItemAnimator)?.supportsChangeAnimations = false

        manageCoins.setOnSingleClickListener { viewModel.delegate.openManageCoins() }

        pullToRefresh.setOnRefreshListener {
            viewModel.delegate.refresh()
        }
    }

    private fun reloadHeader() {
        val headerViewItem = viewModel.delegate.getHeaderViewItem()

        context?.let {
            val color = if (headerViewItem.upToDate) R.color.yellow_crypto else R.color.yellow_crypto_40
            ballanceText.setTextColor(ContextCompat.getColor(it, color))
        }

        ballanceText.text = headerViewItem.currencyValue?.let {
            ValueFormatter.format(it)
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()
        recyclerCoins.adapter = null
    }

    override fun onSendClicked(position: Int) {
        viewModel.onSendClicked(position)
    }

    override fun onReceiveClicked(position: Int) {
        viewModel.onReceiveClicked(position)
    }

    override fun onItemClick(position: Int) {
        coinsAdapter.toggleViewHolder(position)
    }
}

class CoinsAdapter(private val listener: Listener) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    interface Listener {
        fun onSendClicked(position: Int)
        fun onReceiveClicked(position: Int)
        fun onItemClick(position: Int)
    }

    private var expandedViewPosition: Int? = null

    lateinit var viewDelegate: BalanceModule.IViewDelegate

    fun toggleViewHolder(position: Int) {
        expandedViewPosition?.let {
            notifyItemChanged(it, false)
        }

        if (expandedViewPosition != position) {
            notifyItemChanged(position, true)
        }

        expandedViewPosition = if (expandedViewPosition == position) null else position
    }

    override fun getItemCount() = viewDelegate.itemsCount

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
            ViewHolderCoin(LayoutInflater.from(parent.context).inflate(R.layout.view_holder_coin, parent, false), listener)

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {}

    override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
        super.onViewDetachedFromWindow(holder)
        when (holder) {
            is ViewHolderCoin -> holder.unbind()
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: MutableList<Any>) {
        if (holder !is ViewHolderCoin) return

        if (payloads.isEmpty()) {
            holder.bind(viewDelegate.getViewItem(position), expandedViewPosition == position)
        } else if (payloads.any { it is Boolean }) {
            holder.bindPartial(expandedViewPosition == position)
        }
    }
}

class ViewHolderCoin(override val containerView: View, private val listener: CoinsAdapter.Listener) : RecyclerView.ViewHolder(containerView), LayoutContainer {

    private var disposable: Disposable? = null

    fun bind(balanceViewItem: BalanceViewItem, expanded: Boolean) {
        buttonPay.isEnabled = false
        imgSyncFailed.visibility = View.GONE
        textCurrencyAmount.visibility = View.GONE
        textCoinAmount.visibility = View.GONE
        progressSync.visibility = View.GONE
        textSyncProgress.visibility = View.GONE

        balanceViewItem.state.let { adapterState ->
            when (adapterState) {
                is AdapterState.Syncing -> {
                    progressSync.visibility = View.VISIBLE
                    textSyncProgress.visibility = View.VISIBLE

                    disposable?.dispose()
                    disposable = adapterState.progressSubject
                            .observeOn(AndroidSchedulers.mainThread())
                            .subscribe {
                                val progress = (it * 100).toInt()
                                textSyncProgress.text = "$progress%"
                            }
                }
                is AdapterState.Synced -> {
                    if (balanceViewItem.coinValue.value > BigDecimal.ZERO) {
                        textCurrencyAmount.visibility = View.VISIBLE
                        textCurrencyAmount.text = balanceViewItem.currencyValue?.let { ValueFormatter.format(it) }
                        textCurrencyAmount.setTextColor(ContextCompat.getColor(containerView.context, if (balanceViewItem.rateExpired) R.color.yellow_crypto_40 else R.color.yellow_crypto))
                        buttonPay.isEnabled = true
                    }

                    textCoinAmount.visibility = View.VISIBLE
                    textCoinAmount.text = ValueFormatter.format(balanceViewItem.coinValue)
                }
                is AdapterState.NotSynced -> {
                    imgSyncFailed.visibility = View.VISIBLE
                }
            }
        }

        val iconDrawable = ContextCompat.getDrawable(containerView.context, LayoutHelper.getCoinDrawableResource(TextHelper.getCleanCoinCode(balanceViewItem.coinValue.coinCode)))
        imgCoin.setImageDrawable(iconDrawable)

        textCoinName.text = balanceViewItem.coinValue.coinCode

        textExchangeRate.text = balanceViewItem.exchangeValue?.let { exchangeValue ->
            containerView.context.getString(R.string.Balance_RatePerCoin, ValueFormatter.format(exchangeValue), balanceViewItem.coinValue.coinCode)
        } ?: ""
        textExchangeRate.setTextColor(ContextCompat.getColor(containerView.context, if (balanceViewItem.rateExpired) R.color.steel_40 else R.color.grey))

        buttonPay.setOnSingleClickListener {
            listener.onSendClicked(adapterPosition)
        }

        buttonReceive.setOnSingleClickListener {
            listener.onReceiveClicked(adapterPosition)
        }

        viewHolderRoot.isSelected = expanded
        buttonsWrapper.visibility = if (expanded) View.VISIBLE else View.GONE
        containerView.setOnClickListener {
            listener.onItemClick(adapterPosition)
        }
    }

    fun bindPartial(expanded: Boolean) {
        viewHolderRoot.isSelected = expanded
        if (expanded) {
            AnimationHelper.expand(buttonsWrapper)
        } else {
            AnimationHelper.collapse(buttonsWrapper)
        }

    }

    fun unbind() {
        disposable?.dispose()
    }

}

package io.horizontalsystems.bankwallet.modules.balance

import android.os.Handler
import io.horizontalsystems.bankwallet.core.ICurrencyManager
import io.horizontalsystems.bankwallet.core.IRateStorage
import io.horizontalsystems.bankwallet.core.IWalletManager
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers

class BalanceInteractor(
        private val walletManager: IWalletManager,
        private val rateStorage: IRateStorage,
        private val currencyManager: ICurrencyManager,
        private val refreshTimeout: Double = 2.0
) : BalanceModule.IInteractor {

    var delegate: BalanceModule.IInteractorDelegate? = null

    private var disposables: CompositeDisposable = CompositeDisposable()
    private var walletDisposables: CompositeDisposable = CompositeDisposable()
    private var rateDisposables: CompositeDisposable = CompositeDisposable()

    override fun initWallets() {
        onUpdateWallets()

        disposables.add(walletManager.walletsUpdatedSignal
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .subscribe {
                    onUpdateWallets()
                })

        onUpdateCurrency()

        disposables.add(currencyManager.baseCurrencyUpdatedSignal
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .subscribe {
                    onUpdateCurrency()
                })
    }

    override fun fetchRates(currencyCode: String, coinCodes: List<CoinCode>) {
        rateDisposables.clear()

        coinCodes.forEach {
            rateDisposables.add(rateStorage.latestRateObservable(it, currencyCode)
                    .subscribeOn(Schedulers.io())
                    .observeOn(Schedulers.io())
                    .subscribe {
                        delegate?.didUpdateRate(it)
                    })
        }
    }

    private fun onUpdateCurrency() {
        delegate?.didUpdateCurrency(currencyManager.baseCurrency)
    }

    private fun onUpdateWallets() {
        val wallets = walletManager.wallets

        delegate?.didUpdateWallets(wallets)

        walletDisposables.clear()

        wallets.forEach { wallet ->
            walletDisposables.add(wallet.adapter.balanceObservable
                    .subscribeOn(Schedulers.io())
                    .observeOn(Schedulers.io())
                    .subscribe { balance ->
                        delegate?.didUpdateBalance(wallet.coinCode, balance)
                    })

            walletDisposables.add(wallet.adapter.stateObservable
                    .subscribeOn(Schedulers.io())
                    .observeOn(Schedulers.io())
                    .subscribe { state ->
                        delegate?.didUpdateState(wallet.coinCode, state)
                    })
        }
    }

    override fun refresh() {
        walletManager.refreshWallets()

        Handler().postDelayed({
            delegate?.didRefresh()
        }, (refreshTimeout * 1000).toLong())
    }

}

package io.horizontalsystems.bankwallet.modules.balance

import io.horizontalsystems.bankwallet.core.AdapterState
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.entities.Currency
import io.horizontalsystems.bankwallet.entities.Rate
import io.horizontalsystems.bankwallet.entities.Wallet
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode
import java.math.BigDecimal

object BalanceModule {

    interface IView {
        fun didRefresh()
        fun reload()
        fun updateItem(position: Int)
        fun updateHeader()
    }

    interface IViewDelegate {
        val itemsCount: Int

        fun viewDidLoad()
        fun getViewItem(position: Int): BalanceViewItem
        fun getHeaderViewItem(): BalanceHeaderViewItem
        fun refresh()
        fun onReceive(position: Int)
        fun onPay(position: Int)
        fun openManageCoins()
    }

    interface IInteractor {
        fun refresh()
        fun initWallets()
        fun fetchRates(currencyCode: String, coinCodes: List<CoinCode>)
    }

    interface IInteractorDelegate {
        fun didUpdateWallets(wallets: List<Wallet>)
        fun didUpdateCurrency(currency: Currency)
        fun didUpdateBalance(coinCode: CoinCode, balance: BigDecimal)
        fun didUpdateState(coinCode: String, state: AdapterState)
        fun didUpdateRate(rate: Rate)
        fun didRefresh()
    }

    interface IRouter {
        fun openReceiveDialog(coin: String)
        fun openSendDialog(coin: String)
        fun openManageCoins()
    }

    class BalanceItemDataSource {
        val count
            get() = items.count()

        var currency: Currency? = null

        val coinCodes: List<CoinCode>
            get() = items.map { it.coinCode }

        var items = listOf<BalanceItem>()

        fun getItem(position: Int): BalanceItem = items[position]

        fun reset(items: List<BalanceItem>) {
            this.items = items
        }

        fun getPosition(coinCode: CoinCode): Int {
            return items.indexOfFirst { it.coinCode == coinCode }
        }

        fun setBalance(position: Int, balance: BigDecimal) {
            items[position].balance = balance
        }

        fun setState(position: Int, state: AdapterState) {
            items[position].state = state
        }

        fun setRate(position: Int, rate: Rate) {
            items[position].rate = rate
        }

        fun clearRates() {
            items.forEach {
                it.rate = null
            }
        }
    }

    data class BalanceItem(
            val title: String,
            val coinCode: CoinCode,
            var balance: BigDecimal = BigDecimal.ZERO,
            var state: AdapterState = AdapterState.NotSynced,
            var rate: Rate? = null
    )

    fun init(view: BalanceViewModel, router: IRouter) {
        val interactor = BalanceInteractor(App.walletManager, App.rateStorage, App.currencyManager)
        val presenter = BalancePresenter(interactor, router, BalanceItemDataSource(), BalanceViewItemFactory())

        presenter.view = view
        interactor.delegate = presenter
        view.delegate = presenter
    }

}

package io.horizontalsystems.bankwallet.modules.balance

import io.horizontalsystems.bankwallet.core.AdapterState
import io.horizontalsystems.bankwallet.entities.Currency
import io.horizontalsystems.bankwallet.entities.Rate
import io.horizontalsystems.bankwallet.entities.Wallet
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode
import java.math.BigDecimal

class BalancePresenter(
        private var interactor: BalanceModule.IInteractor,
        private val router: BalanceModule.IRouter,
        private val dataSource: BalanceModule.BalanceItemDataSource,
        private val factory: BalanceViewItemFactory) : BalanceModule.IViewDelegate, BalanceModule.IInteractorDelegate {

    var view: BalanceModule.IView? = null

    //
    // BalanceModule.IViewDelegate
    //
    override val itemsCount: Int
        get() = dataSource.count

    override fun viewDidLoad() {
        interactor.initWallets()
    }

    override fun getViewItem(position: Int) =
            factory.createViewItem(dataSource.getItem(position), dataSource.currency)

    override fun getHeaderViewItem() =
            factory.createHeaderViewItem(dataSource.items, dataSource.currency)

    override fun refresh() {
        interactor.refresh()
    }

    override fun onReceive(position: Int) {
        router.openReceiveDialog(dataSource.getItem(position).coinCode)
    }

    override fun onPay(position: Int) {
        router.openSendDialog(dataSource.getItem(position).coinCode)
    }

    //
    // BalanceModule.IInteractorDelegate
    //
    override fun didUpdateWallets(wallets: List<Wallet>) {
        dataSource.reset(wallets.map { BalanceModule.BalanceItem(it.title, it.coinCode) })
        dataSource.currency?.let {
            interactor.fetchRates(it.code, dataSource.coinCodes)
        }

        view?.reload()
    }

    override fun didUpdateCurrency(currency: Currency) {
        dataSource.currency = currency
        dataSource.clearRates()
        interactor.fetchRates(currency.code, dataSource.coinCodes)
        view?.reload()
    }

    override fun didUpdateBalance(coinCode: CoinCode, balance: BigDecimal) {
        val position = dataSource.getPosition(coinCode)
        dataSource.setBalance(position, balance)
        view?.updateItem(position)
        view?.updateHeader()
    }

    override fun didUpdateState(coinCode: String, state: AdapterState) {
        val position = dataSource.getPosition(coinCode)
        dataSource.setState(position, state)
        view?.updateItem(position)
        view?.updateHeader()
    }

    override fun didUpdateRate(rate: Rate) {
        val position = dataSource.getPosition(rate.coinCode)
        dataSource.setRate(position, rate)
        view?.updateItem(position)
        view?.updateHeader()
    }

    override fun didRefresh() {
        view?.didRefresh()
    }

    override fun openManageCoins() {
        router.openManageCoins()
    }

}

package io.horizontalsystems.bankwallet.modules.balance

import io.horizontalsystems.bankwallet.core.AdapterState
import io.horizontalsystems.bankwallet.entities.CoinValue
import io.horizontalsystems.bankwallet.entities.Currency
import io.horizontalsystems.bankwallet.entities.CurrencyValue
import java.math.BigDecimal

data class BalanceViewItem(
        val coinValue: CoinValue,
        val exchangeValue: CurrencyValue?,
        val currencyValue: CurrencyValue?,
        val state: AdapterState,
        val rateExpired: Boolean
)

data class BalanceHeaderViewItem(
        val currencyValue: CurrencyValue?,
        val upToDate: Boolean
)

class BalanceViewItemFactory {

    fun createViewItem(item: BalanceModule.BalanceItem, currency: Currency?): BalanceViewItem {
        var exchangeValue: CurrencyValue? = null
        var currencyValue: CurrencyValue? = null

        item.rate?.let { rate ->
            currency?.let {
                exchangeValue = CurrencyValue(it, rate.value)
                currencyValue = CurrencyValue(it, rate.value * item.balance)
            }
        }

        return BalanceViewItem(
                CoinValue(item.coinCode, item.balance),
                exchangeValue,
                currencyValue,
                item.state,
                item.rate?.expired ?: false
        )
    }

    fun createHeaderViewItem(items: List<BalanceModule.BalanceItem>, currency: Currency?): BalanceHeaderViewItem {
        var sum = BigDecimal.ZERO
        items.forEach {
            sum = sum.plus(it.rate?.value?.times(it.balance) ?: BigDecimal.ZERO)
        }
        val currencyValue = currency?.let {
            CurrencyValue(it, sum)
        }

        val upToDate = items.none { it.state != AdapterState.Synced || it.rate == null || it.rate?.expired == true }

        return BalanceHeaderViewItem(currencyValue, upToDate)
    }

}

package io.horizontalsystems.bankwallet.modules.balance

import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel
import io.horizontalsystems.bankwallet.SingleLiveEvent
import io.horizontalsystems.bankwallet.entities.CurrencyValue

class BalanceViewModel : ViewModel(), BalanceModule.IView, BalanceModule.IRouter {

    lateinit var delegate: BalanceModule.IViewDelegate

    val totalBalanceLiveData = MutableLiveData<CurrencyValue>()
    val openSendDialog = SingleLiveEvent<String>()
    val openReceiveDialog = SingleLiveEvent<String>()
    val balanceColorLiveDate = MutableLiveData<Int>()
    val didRefreshLiveEvent = SingleLiveEvent<Void>()
    val openManageCoinsLiveEvent = SingleLiveEvent<Void>()

    val reloadLiveEvent = SingleLiveEvent<Void>()
    val reloadHeaderLiveEvent = SingleLiveEvent<Void>()
    val reloadItemLiveEvent = SingleLiveEvent<Int>()

    fun init() {
        BalanceModule.init(this, this)

        delegate.viewDidLoad()
    }

    override fun reload() {
        reloadLiveEvent.postValue(null)
    }

    override fun updateItem(position: Int) {
        reloadItemLiveEvent.postValue(position)
    }

    override fun updateHeader() {
        reloadHeaderLiveEvent.postValue(null)
    }

    override fun openReceiveDialog(coin: String) {
        openReceiveDialog.value = coin
    }

    override fun openSendDialog(coin: String) {
        openSendDialog.value = coin
    }

    fun onReceiveClicked(position: Int) {
        delegate.onReceive(position)
    }

    fun onSendClicked(position: Int) {
        delegate.onPay(position)
    }

    override fun didRefresh() {
        didRefreshLiveEvent.call()
    }

    override fun openManageCoins() {
        openManageCoinsLiveEvent.call()
    }
}

package io.horizontalsystems.bankwallet.modules.fulltransactioninfo

import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.text.SpannableString
import android.text.style.UnderlineSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import io.horizontalsystems.bankwallet.BaseActivity
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.entities.FullTransactionItem
import io.horizontalsystems.bankwallet.modules.fulltransactioninfo.dataprovider.DataProviderSettingsModule
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode
import io.horizontalsystems.bankwallet.viewHelpers.HudHelper
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.activity_full_transaction_info.*
import kotlinx.android.synthetic.main.view_holder_full_transaction.*
import kotlinx.android.synthetic.main.view_holder_full_transaction_item.*
import kotlinx.android.synthetic.main.view_holder_full_transaction_provider.*

class FullTransactionInfoActivity : BaseActivity(), FullTransactionInfoErrorFragment.Listener {

    private val transactionRecordAdapter = SectionViewAdapter(this)
    private lateinit var viewModel: FullTransactionInfoViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val transactionHash = intent.extras.getString(transactionHashKey)
        val coinCodeString = intent.extras.getString(coinCodeKey)

        viewModel = ViewModelProviders.of(this).get(FullTransactionInfoViewModel::class.java)
        viewModel.init(transactionHash, coinCodeString)

        setContentView(R.layout.activity_full_transaction_info)

        setSupportActionBar(toolbar)
        supportActionBar?.title = getString(R.string.FullInfo_Title)

        closeBtn.setOnClickListener { onBackPressed() }
        shareBtn.setOnClickListener { viewModel.share() }

        //
        // LiveData
        //
        viewModel.reloadLiveEvent.observe(this, Observer {
            recyclerTransactionInfo.visibility = View.VISIBLE
            transactionRecordAdapter.notifyDataSetChanged()
        })

        viewModel.loadingLiveData.observe(this, Observer { coinCode ->
            if (coinCode == true) {
                progressLoading.visibility = View.VISIBLE
                recyclerTransactionInfo.visibility = View.INVISIBLE
                transactionRecordAdapter.notifyDataSetChanged()
            } else {
                progressLoading.visibility = View.INVISIBLE
            }
        })

        viewModel.showCopiedLiveEvent.observe(this, Observer {
            HudHelper.showSuccessMessage(R.string.Hud_Text_Copied)
        })

        viewModel.openLinkLiveEvent.observe(this, Observer { url ->
            url?.let {
                val intent = Intent(Intent.ACTION_VIEW)
                intent.data = Uri.parse(url)
                startActivity(intent)
            }
        })

        viewModel.openProviderSettingsEvent.observe(this, Observer { data ->
            data?.let { (coinCode, transactionHash) ->
                DataProviderSettingsModule.start(this, coinCode, transactionHash)
            }
        })

        viewModel.showErrorLiveEvent.observe(this, Observer { error ->
            error?.let { (show, providerName) ->
                if (show && providerName != null) {
                    errorContainer.visibility = View.VISIBLE

                    val fragment = FullTransactionInfoErrorFragment.newInstance(providerName)
                    val transaction = supportFragmentManager.beginTransaction()

                    transaction.replace(R.id.errorContainer, fragment)
                    transaction.commit()
                } else {
                    errorContainer.visibility = View.INVISIBLE
                }
            }
        })

        viewModel.showShareLiveEvent.observe(this, Observer { url ->
            val sendIntent: Intent = Intent().apply {
                action = Intent.ACTION_SEND
                putExtra(Intent.EXTRA_TEXT, url)
                type = "text/plain"
            }
            startActivity(sendIntent)
        })

        recyclerTransactionInfo.hasFixedSize()
        recyclerTransactionInfo.adapter = transactionRecordAdapter
        recyclerTransactionInfo.layoutManager = LinearLayoutManager(this)

        transactionRecordAdapter.viewModel = viewModel
    }

    //
    // FullTransactionInfoErrorFragment Listener
    //
    override fun onRetry() {
        viewModel.retry()
    }

    override fun onChangeProvider() {
        viewModel.changeProvider()
    }

    companion object {
        const val transactionHashKey = "transaction_hash"
        const val coinCodeKey = "coin_code"

        fun start(context: Context, transactionHash: String, coinCode: CoinCode) {
            val intents = Intent(context, FullTransactionInfoActivity::class.java)
            intents.putExtra(transactionHashKey, transactionHash)
            intents.putExtra(coinCodeKey, coinCode)
            context.startActivity(intents)
        }
    }
}

class SectionViewAdapter(val context: Context) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    lateinit var viewModel: FullTransactionInfoViewModel

    private val sectionView = 1
    private val sectionViewProvider = 2

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val view = LayoutInflater.from(parent.context)
        return if (viewType == sectionView) {
            SectionViewHolder(view.inflate(R.layout.view_holder_full_transaction, parent, false))
        } else {
            SectionProviderViewHolder(view.inflate(R.layout.view_holder_full_transaction_provider, parent, false))
        }
    }

    override fun getItemViewType(position: Int): Int {
        return if (position == itemCount - 1) {
            sectionViewProvider
        } else {
            sectionView
        }
    }

    override fun getItemCount(): Int {
        return viewModel.delegate.sectionCount + 1
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder) {
            is SectionViewHolder -> {
                viewModel.delegate.getSection(position)?.let { section ->
                    holder.sectionRecyclerView.hasFixedSize()
                    holder.sectionRecyclerView.isNestedScrollingEnabled = false

                    holder.sectionRecyclerView.layoutManager = LinearLayoutManager(context)
                    holder.sectionRecyclerView.adapter = SectionItemViewAdapter(context, viewModel, section.items)
                }

            }
            is SectionProviderViewHolder -> {
                val providerName = viewModel.delegate.providerName
                holder.sectionProvider.bind(title = context.getString(R.string.FullInfo_Source), value = providerName, dimmed = false, icon = null)
                holder.sectionProvider.setOnClickListener {
                    viewModel.delegate.onTapProvider()
                }

                providerName?.let {
                    val changeProviderStyle = SpannableString(providerName)
                    changeProviderStyle.setSpan(UnderlineSpan(), 0, changeProviderStyle.length, 0)

                    holder.providerSite.text = changeProviderStyle
                    holder.providerSite.setOnClickListener {
                        viewModel.delegate.onTapResource()
                    }
                }
            }
        }
    }
}

class SectionItemViewAdapter(val context: Context, val viewModel: FullTransactionInfoViewModel, val items: List<FullTransactionItem>)
    : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val view = LayoutInflater.from(parent.context)
                .inflate(R.layout.view_holder_full_transaction_item, parent, false)

        return SectionItemViewHolder(view)
    }

    override fun getItemCount(): Int {
        return items.size
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder) {
            is SectionItemViewHolder -> {
                val item = items[position]
                val notLast = items.size != position + 1

                bindTransaction(item, notLast, holder.sectionItem)
            }
        }
    }

    private fun bindTransaction(item: FullTransactionItem, showBorder: Boolean, viewItem: FullTransactionInfoItemView) {
        val title = if (item.titleResId != null) context.getString(item.titleResId) else item.title

        viewItem.bind(title, item.value, item.icon, item.dimmed, showBorder)

        if (item.clickable) {
            viewItem.setOnClickListener {
                viewModel.delegate.onTapItem(item)
            }
        }
    }
}

class SectionViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView), LayoutContainer
class SectionProviderViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView), LayoutContainer
class SectionItemViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView), LayoutContainer

package io.horizontalsystems.bankwallet.modules.fulltransactioninfo

import com.google.gson.JsonObject
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.entities.FullTransactionIcon
import io.horizontalsystems.bankwallet.entities.FullTransactionItem
import io.horizontalsystems.bankwallet.entities.FullTransactionRecord
import io.horizontalsystems.bankwallet.entities.FullTransactionSection
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode
import io.horizontalsystems.bankwallet.viewHelpers.DateHelper
import io.horizontalsystems.bankwallet.viewHelpers.ValueFormatter

class FullTransactionBitcoinAdapter(val provider: FullTransactionInfoModule.BitcoinForksProvider, val coinCode: CoinCode)
    : FullTransactionInfoModule.Adapter {

    override fun convert(json: JsonObject): FullTransactionRecord {
        val data = provider.convert(json)
        val sections = mutableListOf<FullTransactionSection>()

        sections.add(FullTransactionSection(items = listOf(
                FullTransactionItem(R.string.FullInfo_Id, value = data.hash, clickable = true, icon = FullTransactionIcon.HASH)
        )))

        val blockItems = mutableListOf(
                FullTransactionItem(R.string.FullInfo_Time, value = DateHelper.getFullDateWithShortMonth(data.date), icon = FullTransactionIcon.TIME),
                FullTransactionItem(R.string.FullInfo_Block, value = data.height.toString(), icon = FullTransactionIcon.BLOCK)
        )

        data.confirmations?.let {
            blockItems.add(FullTransactionItem(R.string.FullInfo_Confirmations, value = data.confirmations, icon = FullTransactionIcon.CHECK))
        }

        sections.add(FullTransactionSection(items = blockItems))

        val transactionItems = mutableListOf(FullTransactionItem(R.string.FullInfo_Fee, value = "${ValueFormatter.format(data.fee)} $coinCode"))

        data.size?.let {
            transactionItems.add(FullTransactionItem(R.string.FullInfo_Size, value = "$it (bytes)", dimmed = true))
        }

        data.feePerByte?.let { feePerByte ->
            transactionItems.add(FullTransactionItem(R.string.FullInfo_Rate, value = "${ValueFormatter.format(feePerByte)} (satoshi)", dimmed = true))
        }

        sections.add(FullTransactionSection(items = transactionItems))

        if (data.inputs.isNotEmpty()) {
            val totalInput = ValueFormatter.format(data.inputs.sumByDouble { it.value })
            val inputs = mutableListOf(FullTransactionItem(R.string.FullInfo_SubtitleInputs, value = "$totalInput $coinCode"))
            data.inputs.map {
                val amount = ValueFormatter.format(it.value)
                inputs.add(FullTransactionItem(title = "$amount $coinCode", value = it.address, clickable = true, icon = FullTransactionIcon.PERSON))
            }

            sections.add(FullTransactionSection(inputs))
        }

        if (data.outputs.isNotEmpty()) {
            val totalOutput = ValueFormatter.format(data.outputs.sumByDouble { it.value })
            val outputs = mutableListOf(FullTransactionItem(R.string.FullInfo_SubtitleOutputs, value = "$totalOutput $coinCode"))

            data.outputs.map {
                val amount = ValueFormatter.format(it.value)
                outputs.add(FullTransactionItem(title = "$amount $coinCode", value = it.address, clickable = true, icon = FullTransactionIcon.PERSON))
            }

            sections.add(FullTransactionSection(outputs))
        }

        return FullTransactionRecord(provider.name, sections)
    }

}

class FullTransactionEthereumAdapter(val provider: FullTransactionInfoModule.EthereumForksProvider, val coinCode: CoinCode)
    : FullTransactionInfoModule.Adapter {

    override fun convert(json: JsonObject): FullTransactionRecord {
        val data = provider.convert(json)
        val sections = mutableListOf<FullTransactionSection>()

        sections.add(FullTransactionSection(items = listOf(
                FullTransactionItem(R.string.FullInfo_Id, value = data.hash, clickable = true, icon = FullTransactionIcon.HASH)
        )))

        val blockItems = mutableListOf<FullTransactionItem>()
        data.date?.let { FullTransactionItem(R.string.FullInfo_Time, value = DateHelper.getFullDateWithShortMonth(it)) }
        blockItems.add(FullTransactionItem(R.string.FullInfo_Block, value = data.height))
        data.confirmations?.let { FullTransactionItem(R.string.FullInfo_Confirmations, value = it.toString()) }

        sections.add(FullTransactionSection(items = blockItems))

        blockItems.add(FullTransactionItem(R.string.FullInfo_GasLimit, value = "${data.gasLimit} GWei", dimmed = true))
        blockItems.add(FullTransactionItem(R.string.FullInfo_GasPrice, value = "${data.gasPrice} GWei", dimmed = true))

        val transactionItems = mutableListOf<FullTransactionItem>()
        data.fee?.let {
            transactionItems.add(FullTransactionItem(R.string.FullInfo_Fee, value = "${ValueFormatter.format(it.toDouble())} $coinCode"))
        }

        if (data.size != null) {
            transactionItems.add(FullTransactionItem(R.string.FullInfo_Size, value = "${data.size} (bytes)", dimmed = true))
        }

        data.gasUsed?.let {
            transactionItems.add(FullTransactionItem(R.string.FullInfo_GasUsed, value = "${data.gasUsed} GWei", dimmed = true))
        }

        transactionItems.add(FullTransactionItem(R.string.FullInfoEth_Nonce, value = data.nonce, dimmed = true))

        if (transactionItems.size > 0)
            sections.add(FullTransactionSection(items = transactionItems))

        sections.add(FullTransactionSection(items = listOf(
                FullTransactionItem(R.string.FullInfoEth_Value, value = "${data.value} $coinCode"),
                FullTransactionItem(R.string.FullInfo_From, value = data.from, clickable = true, icon = FullTransactionIcon.PERSON),
                FullTransactionItem(R.string.FullInfo_To, value = data.to, clickable = true, icon = FullTransactionIcon.PERSON)
        )))

        return FullTransactionRecord(provider.name, sections)
    }

}

package io.horizontalsystems.bankwallet.modules.fulltransactioninfo

import android.content.Context
import android.os.Bundle
import android.support.v4.app.Fragment
import android.text.SpannableString
import android.text.style.UnderlineSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import io.horizontalsystems.bankwallet.R
import kotlinx.android.synthetic.main.fragment_full_transaction_info_error.*

class FullTransactionInfoErrorFragment : Fragment() {

    interface Listener {
        fun onRetry()
        fun onChangeProvider()
    }

    private var provider: String? = null
    private var listener: Listener? = null

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_full_transaction_info_error, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        providerName.text = provider
        providerError.text = getString(R.string.FullInfo_Error_Subtitle)

        val changeProviderStyle = SpannableString(getString(R.string.FullInfo_Error_ChangeSource))
        changeProviderStyle.setSpan(UnderlineSpan(), 0, changeProviderStyle.length, 0)
        changeProvider.text = changeProviderStyle

        btnRetry.setOnClickListener {
            listener?.onRetry()
        }

        changeProvider.setOnClickListener {
            listener?.onChangeProvider()
        }
    }

    override fun onAttach(context: Context) {
        super.onAttach(context)

        if (context is Listener) {
            listener = context
        }
    }

    override fun onDetach() {
        super.onDetach()
        listener = null
    }

    companion object {
        fun newInstance(provider: String): FullTransactionInfoErrorFragment {
            val fragment = FullTransactionInfoErrorFragment()
            fragment.provider = provider
            return fragment
        }
    }
}

package io.horizontalsystems.bankwallet.modules.fulltransactioninfo

import io.horizontalsystems.bankwallet.core.INetworkManager
import io.horizontalsystems.bankwallet.core.ITransactionDataProviderManager
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode

class FullTransactionInfoFactory(private val networkManager: INetworkManager, private val dataProviderManager: ITransactionDataProviderManager)
    : FullTransactionInfoModule.ProviderFactory {

    override fun providerFor(coinCode: CoinCode): FullTransactionInfoModule.FullProvider {
        val baseProvider = dataProviderManager.baseProvider(coinCode)

        val provider: FullTransactionInfoModule.Provider
        val adapter: FullTransactionInfoModule.Adapter

        when {
            // BTC, BTCt
            coinCode.contains("BTC") -> {
                val providerBTC = dataProviderManager.bitcoin(baseProvider.name)

                provider = providerBTC
                adapter = FullTransactionBitcoinAdapter(providerBTC, coinCode)
            }
            // BCH, BCHt
            coinCode.contains("BCH") -> {
                val providerBCH = dataProviderManager.bitcoinCash(baseProvider.name)

                provider = providerBCH
                adapter = FullTransactionBitcoinAdapter(providerBCH, coinCode)
            }
            // ETH, ETHt
            else -> {
                val providerETH = dataProviderManager.ethereum(baseProvider.name)

                provider = providerETH
                adapter = FullTransactionEthereumAdapter(providerETH, coinCode)
            }
        }

        return FullTransactionInfoProvider(networkManager, adapter, provider)
    }
}

package io.horizontalsystems.bankwallet.modules.fulltransactioninfo

import io.horizontalsystems.bankwallet.core.managers.TransactionDataProviderManager
import io.horizontalsystems.bankwallet.entities.FullTransactionRecord
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode
import io.horizontalsystems.bankwallet.viewHelpers.TextHelper
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers

class FullTransactionInfoInteractor(private val transactionInfoFactory: FullTransactionInfoFactory, private var dataProviderManager: TransactionDataProviderManager, private val clipboardManager: TextHelper)
    : FullTransactionInfoModule.Interactor, FullTransactionInfoModule.ProviderDelegate {

    val disposables = CompositeDisposable()
    var delegate: FullTransactionInfoModule.InteractorDelegate? = null

    private var provider: FullTransactionInfoModule.FullProvider? = null

    //
    // Interactor implementations
    //
    override fun didLoad() {
        disposables.add(dataProviderManager.baseProviderUpdatedSignal.subscribe {
            delegate?.onProviderChange()
        })
    }

    override fun updateProvider(coinCode: CoinCode) {
        provider = transactionInfoFactory.providerFor(coinCode)
    }

    override fun url(hash: String): String? {
        return provider?.url(hash)
    }

    override fun retrieveTransactionInfo(transactionHash: String) {
        provider?.let {
            disposables.add(it.retrieveTransactionInfo(transactionHash)
                    .subscribeOn(Schedulers.io())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe({
                        delegate?.onReceiveTransactionInfo(it)
                    }, {
                        delegate?.onError(provider?.providerName)
                    })
            )
        }
    }

    override fun copyToClipboard(value: String) {
        clipboardManager.copyText(value)
    }

    //
    // ProviderDelegate implementations
    //
    override fun onReceiveTransactionInfo(transactionRecord: FullTransactionRecord) {
        delegate?.onReceiveTransactionInfo(transactionRecord)
    }
}

package io.horizontalsystems.bankwallet.modules.fulltransactioninfo

import android.content.Context
import android.support.constraint.ConstraintLayout
import android.support.v4.content.ContextCompat
import android.util.AttributeSet
import android.view.View
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.entities.FullTransactionIcon
import kotlinx.android.synthetic.main.view_transaction_full_info_item.view.*

class FullTransactionInfoItemView : ConstraintLayout {

    private var attrTitle: String? = null
    private var attrValue: String? = null
    private var attrValueIcon: String? = null

    constructor(context: Context) : super(context) {
        initializeViews()
    }

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
        initializeViews()
        loadAttributes(attrs)
    }

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        initializeViews()
        loadAttributes(attrs)
    }

    private fun initializeViews() {
        ConstraintLayout.inflate(context, R.layout.view_transaction_full_info_item, this)
    }

    fun bind(title: String? = null, value: String? = null, icon: FullTransactionIcon?, dimmed: Boolean = false, bottomBorder: Boolean = false) {
        txtTitle.text = title

        var address = false

        when (icon) {
            FullTransactionIcon.PERSON -> value?.let {
                address = true
                addressView.bind(it)
            }
            FullTransactionIcon.HASH -> value?.let {
                address = true
                addressView.bindTransactionId(it, withIcon = false)
                showTypeIcon(R.drawable.hash_grey)
                txtTitle.visibility = View.INVISIBLE
            }
            FullTransactionIcon.TIME -> showTypeIcon(R.drawable.pending_grey)
            FullTransactionIcon.BLOCK -> showTypeIcon(R.drawable.blocks)
            FullTransactionIcon.CHECK -> showTypeIcon(R.drawable.checkmark_grey)
        }

        when {
            address -> {
                valueText.visibility = View.GONE
                addressView.visibility = View.VISIBLE
            }
            else -> {
                valueText.text = value
                valueText.visibility = View.VISIBLE
                addressView.visibility = View.GONE
            }
        }

        if (dimmed) {
            valueText.setTextColor(valueText.resources.getColor(R.color.grey))
        }

        border.visibility = if (bottomBorder) View.VISIBLE else View.GONE
        invalidate()
    }

    private fun showTypeIcon(icon: Int) {
        typeIcon.setImageDrawable(ContextCompat.getDrawable(App.instance, icon))
        typeIcon.visibility = View.VISIBLE
    }

    private fun loadAttributes(attrs: AttributeSet) {
        val ta = context.obtainStyledAttributes(attrs, R.styleable.FullTransactionInfoItemView, 0, 0)
        try {
            attrTitle = ta.getString(R.styleable.FullTransactionInfoItemView_infoTitle)
            attrValue = ta.getString(R.styleable.FullTransactionInfoItemView_infoValue)
            attrValueIcon = ta.getString(R.styleable.FullTransactionInfoItemView_infoValueIcon)
        } finally {
            ta.recycle()
        }
    }

}

package io.horizontalsystems.bankwallet.modules.fulltransactioninfo

import android.support.v4.app.FragmentActivity
import com.google.gson.JsonObject
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.entities.FullTransactionItem
import io.horizontalsystems.bankwallet.entities.FullTransactionRecord
import io.horizontalsystems.bankwallet.entities.FullTransactionSection
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode
import io.horizontalsystems.bankwallet.viewHelpers.TextHelper
import io.reactivex.Flowable

object FullTransactionInfoModule {
    interface View {
        fun show()
        fun reload()
        fun showLoading()
        fun hideLoading()
        fun hideError()
        fun showError(providerName: String?)
        fun showCopied()
        fun openUrl(url: String)
        fun openProviderSettings(coinCode: CoinCode, transactionHash: String)
        fun share(url: String)
    }

    interface ViewDelegate {
        fun viewDidLoad()
        fun onRetryLoad()

        val providerName: String?
        val sectionCount: Int
        fun getSection(row: Int): FullTransactionSection?
        fun onTapItem(item: FullTransactionItem)
        fun onTapProvider()
        fun onTapResource()
        fun onTapChangeProvider()
        fun onShare()
    }

    interface Interactor {
        fun didLoad()
        fun updateProvider(coinCode: CoinCode)

        fun url(hash: String): String?

        fun retrieveTransactionInfo(transactionHash: String)
        fun copyToClipboard(value: String)
    }

    interface InteractorDelegate {
        fun onProviderChange()
        fun onReceiveTransactionInfo(transactionRecord: FullTransactionRecord)
        fun onError(providerName: String?)
        fun retryLoadInfo()
    }

    interface Router

    interface Provider {
        val name: String

        fun url(hash: String): String
        fun apiUrl(hash: String): String
    }

    interface FullProvider {
        val providerName: String
        fun url(hash: String): String

        fun retrieveTransactionInfo(transactionHash: String): Flowable<FullTransactionRecord>
    }

    interface ProvidersMap {
        fun bitcoin(name: String): BitcoinForksProvider
        fun bitcoinCash(name: String): BitcoinForksProvider
        fun ethereum(name: String): EthereumForksProvider
    }

    interface BitcoinForksProvider : Provider {
        fun convert(json: JsonObject): BitcoinResponse
    }

    interface EthereumForksProvider : Provider {
        fun convert(json: JsonObject): EthereumResponse
    }

    interface Adapter {
        fun convert(json: JsonObject): FullTransactionRecord
    }

    interface ProviderFactory {
        fun providerFor(coinCode: CoinCode): FullProvider
    }

    interface ProviderDelegate {
        fun onReceiveTransactionInfo(transactionRecord: FullTransactionRecord)
    }

    interface State {
        val coinCode: CoinCode
        val transactionHash: String
        var transactionRecord: FullTransactionRecord?
    }

    fun init(view: FullTransactionInfoViewModel, router: Router, coinCode: CoinCode, transactionHash: String) {
        val interactor = FullTransactionInfoInteractor(App.transactionInfoFactory, App.transactionDataProviderManager, TextHelper)
        val presenter = FullTransactionInfoPresenter(interactor, router, FullTransactionInfoState(coinCode, transactionHash))

        view.delegate = presenter
        presenter.view = view
        interactor.delegate = presenter
    }

    fun start(activity: FragmentActivity, transactionHash: String, coinCode: CoinCode) {
        FullTransactionInfoActivity.start(activity, transactionHash, coinCode)
    }
}

package io.horizontalsystems.bankwallet.modules.fulltransactioninfo

import io.horizontalsystems.bankwallet.entities.FullTransactionItem
import io.horizontalsystems.bankwallet.entities.FullTransactionRecord
import io.horizontalsystems.bankwallet.entities.FullTransactionSection

class FullTransactionInfoPresenter(val interactor: FullTransactionInfoInteractor, val router: FullTransactionInfoModule.Router, private val state: FullTransactionInfoState)
    : FullTransactionInfoModule.ViewDelegate, FullTransactionInfoModule.InteractorDelegate {

    var view: FullTransactionInfoModule.View? = null

    //
    // State
    //
    override val providerName: String?
        get() = state.transactionRecord?.providerName

    override val sectionCount: Int
        get() = state.transactionRecord?.sections?.size ?: 0

    override fun getSection(row: Int): FullTransactionSection? {
        return state.transactionRecord?.sections?.get(row)
    }

    //
    // ViewDelegate
    //
    override fun viewDidLoad() {
        interactor.didLoad()
        interactor.updateProvider(state.coinCode)

        retryLoadInfo()
    }

    override fun onRetryLoad() {
        if (state.transactionRecord == null) {
            tryLoadInfo()
        }
    }

    override fun onTapItem(item: FullTransactionItem) {
        if (item.clickable) {
            if (item.url != null) {
                view?.openUrl(item.url)
            } else if (item.value != null) {
                interactor.copyToClipboard(item.value)
                view?.showCopied()
            }
        }
    }

    override fun onTapProvider() {
        view?.openProviderSettings(state.coinCode, state.transactionHash)
    }

    override fun onTapChangeProvider() {
        view?.openProviderSettings(state.coinCode, state.transactionHash)
    }

    override fun onTapResource() {
        interactor.url(state.transactionHash)?.let {
            view?.openUrl(it)
        }
    }

    override fun onShare() {
        interactor.url(state.transactionHash)?.let {
            view?.share(it)
        }
    }

    //
    // InteractorDelegate
    //
    override fun onProviderChange() {
        state.transactionRecord = null
        view?.reload()

        interactor.updateProvider(state.coinCode)

        retryLoadInfo()
    }

    override fun onReceiveTransactionInfo(transactionRecord: FullTransactionRecord) {
        state.transactionRecord = transactionRecord
        view?.hideLoading()
        view?.reload()
    }

    override fun onError(providerName: String?) {
        view?.hideLoading()
        view?.showError(providerName)
    }

    override fun retryLoadInfo() {
        if (state.transactionRecord == null) {
            tryLoadInfo()
        }
    }

    //
    // Private
    //
    private fun tryLoadInfo() {
        view?.hideError()
        view?.showLoading()

        interactor.retrieveTransactionInfo(state.transactionHash)
    }
}

package io.horizontalsystems.bankwallet.modules.fulltransactioninfo

import io.horizontalsystems.bankwallet.core.INetworkManager
import io.horizontalsystems.bankwallet.entities.FullTransactionRecord
import io.reactivex.Flowable
import java.net.URL

class FullTransactionInfoProvider(private val networkManager: INetworkManager, private val adapter: FullTransactionInfoModule.Adapter, private val provider: FullTransactionInfoModule.Provider)
    : FullTransactionInfoModule.FullProvider {

    override val providerName: String get() = provider.name

    override fun url(hash: String): String {
        return provider.url(hash)
    }

    override fun retrieveTransactionInfo(transactionHash: String): Flowable<FullTransactionRecord> {
        val uri = provider.apiUrl(transactionHash)
        val url = URL(uri)

        return networkManager
                .getTransaction("${url.protocol}://${url.host}", uri)
                .map { adapter.convert(it) }
    }
}

package io.horizontalsystems.bankwallet.modules.fulltransactioninfo

import io.horizontalsystems.bankwallet.entities.FullTransactionRecord
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode

class FullTransactionInfoState(override val coinCode: CoinCode, override val transactionHash: String)
    : FullTransactionInfoModule.State {

    override var transactionRecord: FullTransactionRecord? = null
}

package io.horizontalsystems.bankwallet.modules.fulltransactioninfo

import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel
import io.horizontalsystems.bankwallet.SingleLiveEvent
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode

class FullTransactionInfoViewModel : ViewModel(), FullTransactionInfoModule.View, FullTransactionInfoModule.Router {

    lateinit var delegate: FullTransactionInfoModule.ViewDelegate

    val loadingLiveData = MutableLiveData<Boolean>()
    val reloadLiveEvent = SingleLiveEvent<Void>()
    val showCopiedLiveEvent = SingleLiveEvent<Unit>()
    val showErrorLiveEvent = SingleLiveEvent<Pair<Boolean, String?>>()
    val showShareLiveEvent = SingleLiveEvent<String>()
    val openLinkLiveEvent = SingleLiveEvent<String>()
    val openProviderSettingsEvent = SingleLiveEvent<Pair<CoinCode, String>>()

    fun init(transactionHash: String, coinCode: CoinCode) {
        FullTransactionInfoModule.init(this, this, coinCode, transactionHash)
        delegate.viewDidLoad()
    }

    fun retry() {
        delegate.onRetryLoad()
    }

    fun share() {
        delegate.onShare()
    }

    fun changeProvider() {
        delegate.onTapChangeProvider()
    }

    //
    // View
    //
    override fun show() {
    }

    override fun showLoading() {
        loadingLiveData.value = true
    }

    override fun hideLoading() {
        loadingLiveData.value = false
    }

    override fun showError(providerName: String?) {
        showErrorLiveEvent.value = Pair(true, providerName)
    }

    override fun hideError() {
        showErrorLiveEvent.value = Pair(false, null)
    }

    override fun reload() {
        reloadLiveEvent.call()
    }

    override fun showCopied() {
        showCopiedLiveEvent.call()
    }

    override fun openProviderSettings(coinCode: CoinCode, transactionHash: String) {
        openProviderSettingsEvent.value = Pair(coinCode, transactionHash)
    }

    override fun openUrl(url: String) {
        openLinkLiveEvent.value = url
    }

    override fun share(url: String) {
        showShareLiveEvent.value = url
    }
}

package io.horizontalsystems.bankwallet.modules.fulltransactioninfo

import java.util.*

interface FullTransactionResponse

abstract class BitcoinResponse : FullTransactionResponse {
    abstract val hash: String
    abstract val date: Date
    abstract val height: Int
    abstract val fee: Double
    abstract val size: Int?
    abstract val feePerByte: Double?
    abstract val confirmations: String?
    abstract val inputs: ArrayList<Input>
    abstract val outputs: ArrayList<Output>

    abstract class Input {
        abstract val value: Double
        abstract val address: String
    }

    abstract class Output {
        abstract val value: Double
        abstract val address: String
    }

    companion object {
        const val btcRate: Double = 100_000_000.0
    }
}

abstract class EthereumResponse : FullTransactionResponse {
    abstract val hash: String
    abstract val date: Date?
    abstract val confirmations: Int?
    abstract val height: String

    abstract val size: Int?
    abstract val gasPrice: String
    abstract val gasUsed: String?
    abstract val gasLimit: String
    abstract val fee: String?
    abstract val value: String

    abstract val nonce: String
    abstract val from: String
    abstract val to: String

    companion object {
        const val ethRate: Double = 1_000_000_000_000_000_000.0
        const val gweiRate: Double = 1_000_000_000.0
    }
}

package io.horizontalsystems.bankwallet.modules.guest

import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import io.horizontalsystems.bankwallet.BaseActivity
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.core.setOnSingleClickListener
import io.horizontalsystems.bankwallet.modules.backup.BackupModule
import io.horizontalsystems.bankwallet.modules.backup.BackupPresenter
import io.horizontalsystems.bankwallet.modules.restore.RestoreModule
import io.horizontalsystems.bankwallet.viewHelpers.HudHelper
import kotlinx.android.synthetic.main.activity_add_wallet.*

class GuestActivity : BaseActivity() {

    private lateinit var viewModel: GuestViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_add_wallet)

        viewModel = ViewModelProviders.of(this).get(GuestViewModel::class.java)
        viewModel.init()

        viewModel.openBackupScreenLiveEvent.observe(this, Observer {
            BackupModule.start(this, BackupPresenter.DismissMode.SET_PIN)
            finish()
        })

        viewModel.openRestoreWalletScreenLiveEvent.observe(this, Observer {
            RestoreModule.start(this)
        })

        viewModel.showErrorDialog.observe(this, Observer {
            HudHelper.showErrorMessage(R.string.Error)
        })

        buttonCreate.setOnSingleClickListener {
            viewModel.delegate.createWalletDidClick()
        }

        buttonRestore.setOnSingleClickListener {
            viewModel.delegate.restoreWalletDidClick()
        }

        viewModel.keyStoreSafeExecute.observe(this, Observer { triple ->
            triple?.let {
                val (action, onSuccess, onFailure) = it
                safeExecuteWithKeystore(action, onSuccess, onFailure)
            }
        })
    }

}

package io.horizontalsystems.bankwallet.modules.guest

import io.horizontalsystems.bankwallet.core.IKeyStoreSafeExecute
import io.horizontalsystems.bankwallet.core.IWordsManager
import io.horizontalsystems.bankwallet.core.managers.AuthManager

class GuestInteractor(
        private val authManager: AuthManager,
        private val wordsManager: IWordsManager,
        private val keystoreSafeExecute: IKeyStoreSafeExecute) : GuestModule.IInteractor {

    var delegate: GuestModule.IInteractorDelegate? = null

    override fun createWallet() {
        keystoreSafeExecute.safeExecute(
                action = Runnable {
                    authManager.login(wordsManager.generateWords())
                },
                onSuccess = Runnable { delegate?.didCreateWallet() },
                onFailure = Runnable { delegate?.didFailToCreateWallet() }
        )
    }

}

package io.horizontalsystems.bankwallet.modules.guest

import android.content.Context
import android.content.Intent
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.core.IKeyStoreSafeExecute

object GuestModule {

    interface IView {
        fun showError()
    }

    interface IViewDelegate {
        fun createWalletDidClick()
        fun restoreWalletDidClick()
    }

    interface IInteractor {
        fun createWallet()
    }

    interface IInteractorDelegate {
        fun didCreateWallet()
        fun didFailToCreateWallet()
    }

    interface IRouter {
        fun navigateToBackupRoutingToMain()
        fun navigateToRestore()
    }

    fun start(context: Context) {
        val intent = Intent(context, GuestActivity::class.java)
        context.startActivity(intent)
    }

    fun init(view: GuestViewModel, router: IRouter, keystoreSafeExecute: IKeyStoreSafeExecute) {
        val interactor = GuestInteractor(App.authManager, App.wordsManager, keystoreSafeExecute)
        val presenter = GuestPresenter(interactor, router)

        view.delegate = presenter
        presenter.view = view
        interactor.delegate = presenter
    }

}

package io.horizontalsystems.bankwallet.modules.guest

class GuestPresenter(private val interactor: GuestModule.IInteractor, private val router: GuestModule.IRouter) : GuestModule.IViewDelegate, GuestModule.IInteractorDelegate {

    var view: GuestModule.IView? = null

    override fun createWalletDidClick() {
        interactor.createWallet()
    }

    override fun restoreWalletDidClick() {
        router.navigateToRestore()
    }

    // interactor delegate

    override fun didCreateWallet() {
        router.navigateToBackupRoutingToMain()
    }

    override fun didFailToCreateWallet() {
        view?.showError()
    }

}

package io.horizontalsystems.bankwallet.modules.guest

import android.arch.lifecycle.ViewModel
import io.horizontalsystems.bankwallet.SingleLiveEvent
import io.horizontalsystems.bankwallet.core.IKeyStoreSafeExecute

class GuestViewModel: ViewModel(), GuestModule.IView, GuestModule.IRouter, IKeyStoreSafeExecute {

    lateinit var delegate: GuestModule.IViewDelegate

    val openBackupScreenLiveEvent = SingleLiveEvent<Void>()
    val openRestoreWalletScreenLiveEvent = SingleLiveEvent<Void>()
    val showErrorDialog = SingleLiveEvent<Void>()
    val keyStoreSafeExecute = SingleLiveEvent<Triple<Runnable, Runnable?, Runnable?>>()

    fun init() {
        GuestModule.init(this, this, this)
    }

    // router
    override fun navigateToBackupRoutingToMain() {
        openBackupScreenLiveEvent.call()
    }

    override fun navigateToRestore() {
        openRestoreWalletScreenLiveEvent.call()
    }

    override fun showError() {
        showErrorDialog.call()
    }

    override fun safeExecute(action: Runnable, onSuccess: Runnable?, onFailure: Runnable?) {
        keyStoreSafeExecute.value = Triple(action, onSuccess, onFailure)
    }
}

package io.horizontalsystems.bankwallet.modules.main

import android.os.Bundle
import android.support.v4.content.ContextCompat
import android.support.v4.view.ViewPager
import com.aurelhubert.ahbottomnavigation.AHBottomNavigation
import com.aurelhubert.ahbottomnavigation.AHBottomNavigationItem
import io.horizontalsystems.bankwallet.BaseActivity
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.viewHelpers.LayoutHelper
import io.reactivex.disposables.Disposable
import kotlinx.android.synthetic.main.activity_dashboard.*

class MainActivity : BaseActivity() {

    private lateinit var adapter: MainTabsAdapter
    private var disposable: Disposable? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_dashboard)

        adapter = MainTabsAdapter(supportFragmentManager)

        viewPager.adapter = adapter
        viewPager.offscreenPageLimit = 2
        viewPager.setPagingEnabled(false)

        LayoutHelper.getAttr(R.attr.BottomNavigationBackgroundColor, theme)?.let {
            bottomNavigation.defaultBackgroundColor = it
        }
        bottomNavigation.accentColor = ContextCompat.getColor(this, R.color.yellow_crypto)
        bottomNavigation.inactiveColor = ContextCompat.getColor(this, R.color.grey)

        bottomNavigation.addItem(AHBottomNavigationItem(R.string.Balance_Title, R.drawable.bank_icon, 0))
        bottomNavigation.addItem(AHBottomNavigationItem(R.string.Transactions_Title, R.drawable.transactions, 0))
        bottomNavigation.addItem(AHBottomNavigationItem(R.string.Settings_Title, R.drawable.settings, 0))
        bottomNavigation.titleState = AHBottomNavigation.TitleState.ALWAYS_HIDE
        bottomNavigation.setUseElevation(false)

        bottomNavigation.setOnTabSelectedListener { position, wasSelected ->
            if (!wasSelected) {
                viewPager.setCurrentItem(position, false)
            }
            true
        }

        viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
            override fun onPageScrollStateChanged(state: Int) {
            }

            override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
                if (positionOffset == 0f) {
                    adapter.currentItem = bottomNavigation.currentItem
                }
            }

            override fun onPageSelected(position: Int) {
                bottomNavigation.currentItem = position
            }
        })

        val activeTab = intent?.extras?.getInt(ACTIVE_TAB_KEY)
        activeTab?.let {
            bottomNavigation.currentItem = it
        }

        disposable = App.appCloseManager.appCloseSignal.subscribe {
            moveTaskToBack(false)
        }
    }

    override fun onBackPressed() {
        if (adapter.currentItem == 1 && adapter.getTransactionFragment().onBackPressed()) {
            return
        } else if (adapter.currentItem > 0) {
            viewPager.currentItem = 0
            return
        }
        super.onBackPressed()
    }

    override fun onDestroy() {
        disposable?.dispose()
        super.onDestroy()
    }

    fun setBottomNavigationVisible(visible: Boolean) {
        bottomNavigation.animate().translationY(if (visible) 0f else (bottomNavigation.height).toFloat()).duration = 150
        bottomNavigationBarShadow.animate().translationY(if (visible) 0f else (bottomNavigation.height).toFloat()).duration = 150
    }

    fun updateSettingsTabCounter(count: Int) {
        val countText = if (count < 1) "" else count.toString()
        val settingsTabPosition = 2
        bottomNavigation.setNotification(countText, settingsTabPosition)
    }

    companion object {
        const val ACTIVE_TAB_KEY = "active_tab"
        const val SETTINGS_TAB_POSITION = 2
    }

}

package io.horizontalsystems.bankwallet.modules.main

import android.content.Context
import android.content.Intent

object MainModule {
    fun start(context: Context) {
        val intent = Intent(context, MainActivity::class.java)
        intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
        context.startActivity(intent)
    }

    fun startAsNewTask(context: Context, activeTab: Int? = null) {
        val intent = Intent(context, MainActivity::class.java)
        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
        activeTab?.let {
            intent.putExtra(MainActivity.ACTIVE_TAB_KEY, it)
        }
        context.startActivity(intent)
    }
}

package io.horizontalsystems.bankwallet.modules.main

import android.support.v4.app.Fragment
import android.support.v4.app.FragmentManager
import android.support.v4.app.FragmentPagerAdapter
import android.util.SparseArray
import android.view.ViewGroup
import io.horizontalsystems.bankwallet.modules.settings.main.MainSettingsFragment
import io.horizontalsystems.bankwallet.modules.transactions.TransactionsFragment
import io.horizontalsystems.bankwallet.modules.balance.BalanceFragment

class MainTabsAdapter(fragmentManager: FragmentManager) : FragmentPagerAdapter(fragmentManager) {

    var currentItem = 0

    private val registeredFragments = SparseArray<Fragment>()

    override fun getCount(): Int = 3

    override fun getItem(position: Int): Fragment = when (position) {
        0 -> BalanceFragment()
        1 -> TransactionsFragment()
        else -> MainSettingsFragment()
    }

    override fun instantiateItem(container: ViewGroup, position: Int): Any {
        val fragment = super.instantiateItem(container, position) as Fragment
        registeredFragments.put(position, fragment)
        return fragment
    }

    override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
        registeredFragments.remove(position)
        super.destroyItem(container, position, `object`)
    }

    fun getTransactionFragment(): TransactionsFragment {
        return registeredFragments[1] as TransactionsFragment
    }

}

package io.horizontalsystems.bankwallet.modules.managecoins

import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v4.content.ContextCompat
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.helper.ItemTouchHelper
import android.view.*
import io.horizontalsystems.bankwallet.BaseActivity
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.core.setOnSingleClickListener
import io.horizontalsystems.bankwallet.entities.Coin
import io.horizontalsystems.bankwallet.viewHelpers.LayoutHelper
import io.horizontalsystems.bankwallet.viewHelpers.TextHelper
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.activity_manage_coins.*
import kotlinx.android.synthetic.main.view_holder_coin_disabled.*
import kotlinx.android.synthetic.main.view_holder_coin_enabled.*

class ManageCoinsActivity : BaseActivity(), ManageCoinsAdapter.Listener, StartDragListener {

    private lateinit var viewModel: ManageCoinsViewModel
    private var itemTouchHelper: ItemTouchHelper? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = ViewModelProviders.of(this).get(ManageCoinsViewModel::class.java)
        viewModel.init()

        setContentView(R.layout.activity_manage_coins)

        val adapter = ManageCoinsAdapter(this, this)
        recyclerView.adapter = adapter
        recyclerView.layoutManager = LinearLayoutManager(this)
        adapter.viewModel = viewModel
        itemTouchHelper = ItemTouchHelper(MyDragHelperCallback(adapter))
        itemTouchHelper?.attachToRecyclerView(recyclerView)

        setSupportActionBar(toolbar)
        supportActionBar?.setDisplayHomeAsUpEnabled(true)
        supportActionBar?.setHomeAsUpIndicator(R.drawable.close)
        supportActionBar?.title = getString(R.string.ManageCoins_title)

        viewModel.coinsLoadedLiveEvent.observe(this, Observer {
            adapter.notifyDataSetChanged()
        })

        viewModel.closeLiveDate.observe(this, Observer {
            finish()
        })
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menuInflater.inflate(R.menu.menu_manage_coins, menu)
        LayoutHelper.tintMenuIcons(menu, ContextCompat.getColor(this, R.color.yellow_crypto))
        return super.onCreateOptionsMenu(menu)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) {
        android.R.id.home -> {
            onBackPressed()
            true
        }
        R.id.action_done -> {
            viewModel.delegate.saveChanges()
            true
        }
        else -> super.onOptionsItemSelected(item)
    }

    override fun onEnabledItemClick(position: Int) {
        viewModel.delegate.disableCoin(position)
    }

    override fun onDisabledItemClick(position: Int) {
        viewModel.delegate.enableCoin(position)
    }

    override fun requestDrag(viewHolder: RecyclerView.ViewHolder) {
        itemTouchHelper?.startDrag(viewHolder)
    }
}

class ManageCoinsAdapter(
        private var listener: Listener,
        private var startDragListener: StartDragListener)
    : RecyclerView.Adapter<RecyclerView.ViewHolder>(), MyDragHelperCallback.Listener {

    interface Listener {
        fun onEnabledItemClick(position: Int)
        fun onDisabledItemClick(position: Int)
    }

    lateinit var viewModel: ManageCoinsViewModel

    private val typeEnabled = 0
    private val typeDisabled = 1
    private val typeDivider = 2

    override fun getItemCount() = viewModel.delegate.enabledCoinsCount + viewModel.delegate.disabledCoinsCount + (if (showDivider) 1 else 0)

    override fun getItemViewType(position: Int): Int = when {
        position < viewModel.delegate.enabledCoinsCount -> typeEnabled
        showDivider && position == viewModel.delegate.enabledCoinsCount -> typeDivider
        else -> typeDisabled
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType) {
            typeEnabled -> ViewHolderEnabledCoin(LayoutInflater.from(parent.context).inflate(R.layout.view_holder_coin_enabled, parent, false))
            typeDisabled -> ViewHolderDisabledCoin(LayoutInflater.from(parent.context).inflate(R.layout.view_holder_coin_disabled, parent, false))
            else -> ViewHolderDivider(LayoutInflater.from(parent.context).inflate(R.layout.view_holder_manage_coins_divider, parent, false))
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder) {
            is ViewHolderEnabledCoin -> {
                val transactionRecord = viewModel.delegate.enabledItemForIndex(position)
                holder.bind(transactionRecord) { listener.onEnabledItemClick(position) }

                holder.dragIcon.setOnTouchListener { _, event ->
                    if (event.action == MotionEvent.ACTION_DOWN) {
                        startDragListener.requestDrag(holder)
                    }
                    false
                }
            }
            is ViewHolderDisabledCoin -> {
                val transactionRecord = viewModel.delegate.disabledItemForIndex(disabledIndex(position))
                holder.bind(transactionRecord) { listener.onDisabledItemClick(disabledIndex(position)) }
            }
        }

    }

    override fun onItemMoved(from: Int, to: Int) {
        notifyItemMoved(from, to)
    }

    override fun onItemMoveEnded(from: Int, to: Int) {
        viewModel.delegate.moveCoin(from, to)
    }

    private val showDivider
        get() = viewModel.delegate.enabledCoinsCount > 0

    private fun disabledIndex(position: Int): Int = when {
        showDivider -> position - viewModel.delegate.enabledCoinsCount - 1
        else -> position
    }
}

class ViewHolderEnabledCoin(override val containerView: View) : RecyclerView.ViewHolder(containerView), LayoutContainer {

    fun bind(coin: Coin, onClick: () -> (Unit)) {
        coinTitle.text = coin.title
        coinCode.text = coin.code

        val iconDrawable = ContextCompat.getDrawable(containerView.context, LayoutHelper.getCoinDrawableResource(TextHelper.getCleanCoinCode(coin.code)))
        coinIcon.setImageDrawable(iconDrawable)
        minusIcon.setOnSingleClickListener { onClick.invoke() }
    }

}

class ViewHolderDisabledCoin(override val containerView: View) : RecyclerView.ViewHolder(containerView), LayoutContainer {

    fun bind(coin: Coin, onClick: () -> (Unit)) {
        disableCoinTitle.text = coin.title
        disableCoinCode.text = coin.code

        val iconDrawable = ContextCompat.getDrawable(containerView.context, LayoutHelper.getCoinDrawableResource(TextHelper.getCleanCoinCode(coin.code)))
        disableCoinIcon.setImageDrawable(iconDrawable)
        plusIcon.setOnSingleClickListener { onClick.invoke() }
    }

}

class ViewHolderDivider(override val containerView: View) : RecyclerView.ViewHolder(containerView), LayoutContainer

class MyDragHelperCallback(private var listener: Listener) : ItemTouchHelper.Callback() {

    var dragFrom = -1
    var dragTo = -1

    interface Listener {
        fun onItemMoved(from: Int, to: Int)
        fun onItemMoveEnded(from: Int, to: Int)
    }

    private val drawMovementFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN

    override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
        return makeMovementFlags(drawMovementFlags, 0)
    }

    override fun canDropOver(recyclerView: RecyclerView, current: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
        return current.itemViewType == target.itemViewType
    }

    override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
        val fromPosition = viewHolder.adapterPosition
        val toPosition = target.adapterPosition
        if(dragFrom == -1) {
            dragFrom =  fromPosition
        }
        dragTo = toPosition

        listener.onItemMoved(viewHolder.adapterPosition, target.adapterPosition)
        return true
    }

    override fun onSwiped(recyclerView: RecyclerView.ViewHolder, position: Int) { }

    override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
        super.clearView(recyclerView, viewHolder)

        if(dragFrom != -1 && dragTo != -1 && dragFrom != dragTo) {
            listener.onItemMoveEnded(dragFrom, dragTo)
        }

        dragFrom = -1
        dragTo = -1
    }
}

interface StartDragListener {
    fun requestDrag(viewHolder: RecyclerView.ViewHolder)
}

package io.horizontalsystems.bankwallet.modules.managecoins

import io.horizontalsystems.bankwallet.core.ICoinManager
import io.horizontalsystems.bankwallet.core.ICoinStorage
import io.horizontalsystems.bankwallet.entities.Coin
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers

class ManageCoinsInteractor(
        private val coinManager: ICoinManager,
        private val coinStorage: ICoinStorage) : ManageCoinsModule.IInteractor {

    var delegate: ManageCoinsModule.IInteractorDelegate? = null
    private val disposables = CompositeDisposable()

    override fun loadCoins() {
        disposables.add(coinManager.allCoinsObservable
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe { allCoins ->
                    delegate?.didLoadAllCoins(allCoins)
                })

        disposables.add(coinStorage.enabledCoinsObservable()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe { enabledCoins ->
                    delegate?.didLoadEnabledCoins(enabledCoins)
                })
    }

    override fun saveEnabledCoins(enabledCoins: List<Coin>) {
        coinStorage.save(enabledCoins)
        delegate?.didSaveChanges()
    }

}

package io.horizontalsystems.bankwallet.modules.managecoins

import android.content.Context
import android.content.Intent
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.entities.Coin

object ManageCoinsModule {

    class ManageCoinsPresenterState {
        var allCoins = listOf<Coin>()
            set(value) {
                field = value
                setDisabledCoins()
            }

        var enabledCoins = mutableListOf<Coin>()
            set(value) {
                field = value
                setDisabledCoins()
            }

        var disabledCoins = listOf<Coin>()

        fun enable(coin: Coin) {
            enabledCoins.add(coin)
            setDisabledCoins()
        }

        fun disable(coin: Coin) {
            enabledCoins.remove(coin)
            setDisabledCoins()
        }

        fun move(coin: Coin, index: Int) {
            enabledCoins.remove(coin)
            enabledCoins.add(index, coin)
        }

        private fun setDisabledCoins() {
            val coins = allCoins
            disabledCoins = coins.minus(enabledCoins)
        }
    }

    interface IView {
        fun updateCoins()
        fun showFailedToSaveError()
    }

    interface IViewDelegate {
        fun viewDidLoad()
        fun enableCoin(position: Int)
        fun disableCoin(position: Int)
        fun saveChanges()
        fun moveCoin(fromIndex: Int, toIndex: Int)

        fun enabledItemForIndex(position: Int): Coin
        fun disabledItemForIndex(position: Int): Coin
        val enabledCoinsCount: Int
        val disabledCoinsCount: Int
    }

    interface IInteractor {
        fun loadCoins()
        fun saveEnabledCoins(enabledCoins: List<Coin>)
    }

    interface IInteractorDelegate {
        fun didLoadEnabledCoins(enabledCoins: List<Coin>)
        fun didLoadAllCoins(allCoins: List<Coin>)
        fun didSaveChanges()
        fun didFailedToSave()
    }

    interface IRouter {
        fun close()
    }

    fun init(view: ManageCoinsViewModel, router: IRouter) {
        val interactor = ManageCoinsInteractor(App.coinManager, App.coinsStorage)
        val presenter = ManageCoinsPresenter(interactor, router, ManageCoinsPresenterState())

        view.delegate = presenter
        presenter.view = view
        interactor.delegate = presenter
    }

    fun start(context: Context) {
        val intent = Intent(context, ManageCoinsActivity::class.java)
        context.startActivity(intent)
    }

}

package io.horizontalsystems.bankwallet.modules.managecoins

import io.horizontalsystems.bankwallet.entities.Coin

class ManageCoinsPresenter(
        private val interactor: ManageCoinsModule.IInteractor,
        private val router: ManageCoinsModule.IRouter,
        private val state: ManageCoinsModule.ManageCoinsPresenterState
) : ManageCoinsModule.IViewDelegate, ManageCoinsModule.IInteractorDelegate {

    var view: ManageCoinsModule.IView? = null

    override fun viewDidLoad() {
        interactor.loadCoins()
    }

    override fun didLoadEnabledCoins(enabledCoins: List<Coin>) {
        state.enabledCoins = enabledCoins.toMutableList()
        view?.updateCoins()
    }

    override fun didLoadAllCoins(allCoins: List<Coin>) {
        state.allCoins = allCoins.toMutableList()
        view?.updateCoins()
    }

    override fun enableCoin(position: Int) {
        state.enable(state.disabledCoins[position])
        view?.updateCoins()
    }

    override fun disableCoin(position: Int) {
        state.disable(state.enabledCoins[position])
        view?.updateCoins()
    }

    override fun moveCoin(fromIndex: Int, toIndex: Int) {
        state.move(state.enabledCoins[fromIndex], toIndex)
        view?.updateCoins()
    }

    override fun saveChanges() {
        interactor.saveEnabledCoins(state.enabledCoins)
    }

    override fun didSaveChanges() {
        router.close()
    }

    override fun didFailedToSave() {
        view?.showFailedToSaveError()
    }

    override fun enabledItemForIndex(position: Int): Coin {
        return state.enabledCoins[position]
    }

    override fun disabledItemForIndex(position: Int): Coin {
        return state.disabledCoins[position]
    }

    override val enabledCoinsCount: Int
        get() = state.enabledCoins.size

    override val disabledCoinsCount: Int
        get() = state.disabledCoins.size
}

package io.horizontalsystems.bankwallet.modules.managecoins

import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel
import io.horizontalsystems.bankwallet.SingleLiveEvent

class ManageCoinsViewModel: ViewModel(), ManageCoinsModule.IView, ManageCoinsModule.IRouter {

    val coinsLoadedLiveEvent = SingleLiveEvent<Void>()
    val closeLiveDate = SingleLiveEvent<Void>()

    lateinit var delegate: ManageCoinsModule.IViewDelegate

    fun init() {
        ManageCoinsModule.init(this, this)
        delegate.viewDidLoad()
    }

    override fun updateCoins() {
        coinsLoadedLiveEvent.call()
    }

    override fun close() {
        closeLiveDate.call()
    }

    override fun showFailedToSaveError() {
        TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
    }
}

package io.horizontalsystems.bankwallet.modules.pin

import io.horizontalsystems.bankwallet.R

open class ManagePinPresenter(
        private val interactor: PinModule.IPinInteractor,
        val pages: List<Page>) : PinModule.IPinViewDelegate, PinModule.IPinInteractorDelegate {

    enum class Page { UNLOCK, ENTER, CONFIRM }

    var view: PinModule.IPinView? = null
    private var enteredPin = ""

    override fun viewDidLoad() {
    }

    override fun onEnter(pin: String, pageIndex: Int) {
        if (enteredPin.length < PinModule.PIN_COUNT) {
            enteredPin += pin
            view?.fillCircles(enteredPin.length, pageIndex)

            if (enteredPin.length == PinModule.PIN_COUNT) {
                navigateToPage(pageIndex, enteredPin)
                enteredPin = ""
            }
        }
    }

    private fun navigateToPage(pageIndex: Int, pin: String) {
        when (pages[pageIndex]) {
            Page.UNLOCK -> onEnterFromUnlock(pin)
            Page.ENTER -> onEnterFromEnterPage(pin)
            Page.CONFIRM -> onEnterFromConfirmPage(pin)
        }
    }

    override fun resetPin() {
        enteredPin = ""
    }

    override fun onDelete(pageIndex: Int) {
        if (enteredPin.isNotEmpty()) {
            enteredPin = enteredPin.substring(0, enteredPin.length - 1)
            view?.fillCircles(enteredPin.length, pageIndex)
        }
    }

    override fun onCancel() {
    }

    override fun didSavePin() {
    }

    override fun didStartedAdapters() {
    }

    override fun onBackPressed() {
    }

    override fun didFailToSavePin() {
        showEnterPage()
        view?.showError(R.string.SetPin_ErrorFailedToSavePin)
    }

    private fun show(page: Page) {
        val pageIndex = pages.indexOfFirst { it == page }
        if (pageIndex >= 0) {
            view?.showPage(pageIndex)
        }
    }

    private fun show(error: Int, page: Page) {
        val pageIndex = pages.indexOfFirst { it == page }
        if (pageIndex >= 0) {
            view?.showErrorForPage(error, pageIndex)
        }
    }

    private fun showEnterPage() {
        interactor.set(null)
        show(Page.ENTER)
    }

    private fun onEnterFromUnlock(pin: String) {
        if (interactor.unlock(pin)) {
            show(Page.ENTER)
        } else {
            val pageUnlockIndex = pages.indexOfFirst { it == Page.UNLOCK }
            if (pageUnlockIndex >= 0) {
                enteredPin = ""
                view?.showPinWrong(pageUnlockIndex)
            }
        }
    }

    private fun onEnterFromEnterPage(pin: String) {
        interactor.set(pin)
        show(Page.CONFIRM)
    }

    private fun onEnterFromConfirmPage(pin: String) {
        if (interactor.validate(pin)) {
            interactor.save(pin)
        } else {
            showEnterPage()
            show(R.string.SetPin_ErrorPinsDontMatch, page = Page.ENTER)
        }
    }
}

package io.horizontalsystems.bankwallet.modules.pin

import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.support.constraint.ConstraintLayout
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat
import android.support.v7.widget.GridLayoutManager
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.PagerSnapHelper
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.animation.AnimationUtils
import android.widget.ImageView
import android.widget.TextView
import io.horizontalsystems.bankwallet.BaseActivity
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.core.security.FingerprintAuthenticationDialogFragment
import io.horizontalsystems.bankwallet.modules.main.MainModule
import io.horizontalsystems.bankwallet.ui.extensions.NumPadItem
import io.horizontalsystems.bankwallet.ui.extensions.NumPadItemType
import io.horizontalsystems.bankwallet.ui.extensions.NumPadItemsAdapter
import io.horizontalsystems.bankwallet.ui.extensions.SmoothLinearLayoutManager
import io.horizontalsystems.bankwallet.viewHelpers.DateHelper
import io.horizontalsystems.bankwallet.viewHelpers.HudHelper
import kotlinx.android.synthetic.main.activity_pin.*
import kotlinx.android.synthetic.main.custom_tall_toolbar.*

class PinActivity : BaseActivity(), NumPadItemsAdapter.Listener, FingerprintAuthenticationDialogFragment.Callback {

    private lateinit var viewModel: PinViewModel
    private lateinit var layoutManager: LinearLayoutManager
    private lateinit var pinPagesAdapter: PinPagesAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)

        setContentView(R.layout.activity_pin)

        setSupportActionBar(toolbar)
        backButton.visibility = View.GONE
        backButton.setOnClickListener { viewModel.delegate.onBackPressed() }

        val interactionType = intent.getSerializableExtra(keyInteractionType) as PinInteractionType

        pinPagesAdapter = PinPagesAdapter()
        layoutManager = SmoothLinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
        pinPagesRecyclerView.layoutManager = layoutManager
        val snapHelper = PagerSnapHelper()
        snapHelper.attachToRecyclerView(pinPagesRecyclerView)
        pinPagesRecyclerView.adapter = pinPagesAdapter

        pinPagesRecyclerView.setOnTouchListener { _, _ ->
            //disable recyclerview scroll
            true
        }

        viewModel = ViewModelProviders.of(this).get(PinViewModel::class.java)
        viewModel.init(interactionType)

        val numpadAdapter = NumPadItemsAdapter(this, NumPadItemType.FINGER)

        numPadItemsRecyclerView.adapter = numpadAdapter
        numPadItemsRecyclerView.layoutManager = GridLayoutManager(this, 3)

        viewModel.titleLiveDate.observe(this, Observer { title ->
            title?.let { toolbarTitle.text = getString(it) }
        })

        viewModel.hideToolbar.observe(this, Observer {
            supportActionBar?.hide()
        })

        viewModel.showBackButton.observe(this, Observer {
            backButton.visibility = View.VISIBLE
        })

        viewModel.titleLiveDate.observe(this, Observer { title ->
            title?.let { supportActionBar?.title = getString(it) }
        })

        viewModel.addPagesEvent.observe(this, Observer { pinPages ->
            pinPages?.let {
                pinPagesAdapter.pinPages.addAll(it)
                pinPagesAdapter.notifyDataSetChanged()
            }
        })

        viewModel.showPageAtIndex.observe(this, Observer { index ->
            index?.let {
                Handler().postDelayed({
                    pinPagesAdapter.setEnteredPinLength(layoutManager.findFirstVisibleItemPosition(), 0)
                    pinPagesRecyclerView.smoothScrollToPosition(it)
                }, 300)
            }
        })

        viewModel.showErrorForPage.observe(this, Observer { errorForPage ->
            errorForPage?.let { (error, pageIndex) ->
                pinPagesAdapter.setErrorForPage(pageIndex, error?.let { getString(error) } ?: null)
            }
        })

        viewModel.showError.observe(this, Observer { error ->
            error?.let {
                HudHelper.showErrorMessage(it)
            }
        })

        viewModel.navigateToMainLiveEvent.observe(this, Observer {
            Handler().postDelayed({
                MainModule.startAsNewTask(this)
                finish()
            }, 300)
        })

        viewModel.keyStoreSafeExecute.observe(this, Observer { triple ->
            triple?.let {
                val (action, onSuccess, onFailure) = it
                safeExecuteWithKeystore(action, onSuccess, onFailure)
            }
        })

        viewModel.fillPinCircles.observe(this, Observer { pair ->
            pair?.let { (length, pageIndex) ->
                pinPagesAdapter.setEnteredPinLength(pageIndex, length)
            }
        })

        viewModel.dismissLiveEvent.observe(this, Observer {
            finish()
        })

        viewModel.showFingerprintInputLiveEvent.observe(this, Observer { cryptoObject ->
            cryptoObject?.let {
                showFingerprintDialog(it)
                numpadAdapter.showFingerPrintButton = true
            }
        })

        viewModel.resetCirclesWithShakeAndDelayForPage.observe(this, Observer { pageIndex ->
            pageIndex?.let {
                pinPagesAdapter.shakePageIndex = it
                pinPagesAdapter.notifyDataSetChanged()
                Handler().postDelayed({
                    pinPagesAdapter.shakePageIndex = null
                    pinPagesAdapter.setEnteredPinLength(pageIndex, 0)
                    viewModel.delegate.resetPin()
                }, 300)
            }
        })

        viewModel.showAttemptsLeftError.observe(this, Observer { pair ->
            pair?.let { (attempts, pageIndex) ->
                pinUnlock.visibility = View.VISIBLE
                pinUnlockBlocked.visibility = View.GONE
            }
        })

        viewModel.showLockedView.observe(this, Observer { untilDate ->
            untilDate?.let {
                pinUnlock.visibility = View.GONE
                pinUnlockBlocked.visibility = View.VISIBLE
                val time = DateHelper.formatDate(it, "HH:mm:ss")
                blockedScreenMessage.text = getString(R.string.UnlockPin_WalletDisabledUntil, time)
            }
        })

        viewModel.closeApplicationLiveEvent.observe(this, Observer {
            App.appCloseManager.appCloseSignal.onNext(Unit)
            moveTaskToBack(false)
        })

    }

    override fun onBackPressed() {
        viewModel.delegate.onBackPressed()
    }

    override fun onItemClick(item: NumPadItem) {
        when (item.type) {
            NumPadItemType.NUMBER -> viewModel.delegate.onEnter(item.number.toString(), layoutManager.findFirstVisibleItemPosition())
            NumPadItemType.DELETE -> viewModel.delegate.onDelete(layoutManager.findFirstVisibleItemPosition())
            NumPadItemType.FINGER -> viewModel.delegate.showBiometricUnlock()
        }
    }

    override fun onFingerprintAuthSucceed() {
        viewModel.delegate.onBiometricUnlock()
    }

    private fun showFingerprintDialog(cryptoObject: FingerprintManagerCompat.CryptoObject) {
        if (App.systemInfoManager.touchSensorCanBeUsed()) {
            val fragment = FingerprintAuthenticationDialogFragment()
            fragment.setCryptoObject(cryptoObject)
            fragment.setCallback(this@PinActivity)
            fragment.isCancelable = true
            fragment.show(fragmentManager, "fingerprint_dialog")
        }
    }

    companion object {

        private const val keyInteractionType = "interaction_type"

        fun start(context: Context, interactionType: PinInteractionType) {
            val intent = Intent(context, PinActivity::class.java)
            intent.putExtra(keyInteractionType, interactionType)
            context.startActivity(intent)
        }

        fun startForUnlock() {
            val intent = Intent(App.instance, PinActivity::class.java)
            intent.putExtra(keyInteractionType, PinInteractionType.UNLOCK)
            intent.flags = Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
            App.instance.startActivity(intent)
        }
    }
}

//PinPage part
class PinPage(val description: Int, var enteredDidgitsLength: Int = 0, var error: String? = null)

class PinPagesAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    var pinPages = mutableListOf<PinPage>()
    var shakePageIndex: Int? = null

    fun setErrorForPage(pageIndex: Int, error: String?) {
        pinPages[pageIndex].error = error
        notifyDataSetChanged()
    }

    fun setEnteredPinLength(pageIndex: Int, enteredLength: Int) {
        pinPages[pageIndex].enteredDidgitsLength = enteredLength
        notifyDataSetChanged()
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return PinPageViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.view_pin_page, parent, false))
    }

    override fun getItemCount() = pinPages.count()

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if (holder is PinPageViewHolder) {
            holder.bind(pinPages[position], shakePageIndex == position)//, { listener.onChangeProvider(numPadItems[position]) }, listener.isFingerPrintEnabled())
        }
    }

}

class PinPageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    private var txtDesc: TextView = itemView.findViewById(R.id.txtDescription)
    private var txtError: TextView = itemView.findViewById(R.id.errorMessage)
    private var pinCirclesWrapper = itemView.findViewById<ConstraintLayout>(R.id.pinCirclesWrapper)

    private var imgPinMask1: ImageView = itemView.findViewById(R.id.imgPinMaskOne)
    private var imgPinMask2: ImageView = itemView.findViewById(R.id.imgPinMaskTwo)
    private var imgPinMask3: ImageView = itemView.findViewById(R.id.imgPinMaskThree)
    private var imgPinMask4: ImageView = itemView.findViewById(R.id.imgPinMaskFour)
    private var imgPinMask5: ImageView = itemView.findViewById(R.id.imgPinMaskFive)
    private var imgPinMask6: ImageView = itemView.findViewById(R.id.imgPinMaskSix)

    fun bind(pinPage: PinPage, shake: Boolean) {
        txtDesc.text = itemView.resources.getString(pinPage.description)
        updatePinCircles(pinPage.enteredDidgitsLength)
        txtError.text = pinPage.error ?: ""
        if (shake) {
            val shakeAnim = AnimationUtils.loadAnimation(itemView.context, R.anim.shake_pin_circles)
            pinCirclesWrapper.startAnimation(shakeAnim)
        }
    }

    private fun updatePinCircles(length: Int) {
        val filledCircle = R.drawable.pin_circle_filled
        val emptyCircle = R.drawable.pin_circle_empty

        imgPinMask1.setImageResource(if (length > 0) filledCircle else emptyCircle)
        imgPinMask2.setImageResource(if (length > 1) filledCircle else emptyCircle)
        imgPinMask3.setImageResource(if (length > 2) filledCircle else emptyCircle)
        imgPinMask4.setImageResource(if (length > 3) filledCircle else emptyCircle)
        imgPinMask5.setImageResource(if (length > 4) filledCircle else emptyCircle)
        imgPinMask6.setImageResource(if (length > 5) filledCircle else emptyCircle)
    }
}

package io.horizontalsystems.bankwallet.modules.pin

import io.horizontalsystems.bankwallet.core.IKeyStoreSafeExecute
import io.horizontalsystems.bankwallet.core.IPinManager
import io.horizontalsystems.bankwallet.core.managers.AuthManager

class PinInteractor(
        private val pinManager: IPinManager,
        private val authManager: AuthManager,
        private val keystoreSafeExecute: IKeyStoreSafeExecute) : PinModule.IPinInteractor {

    var delegate: PinModule.IPinInteractorDelegate? = null
    private var storedPin: String? = null

    override fun set(pin: String?) {
        storedPin = pin
    }

    override fun validate(pin: String): Boolean {
        return storedPin == pin
    }

    override fun save(pin: String) {
        keystoreSafeExecute.safeExecute(
                action = Runnable { pinManager.store(pin) },
                onSuccess = Runnable { delegate?.didSavePin() },
                onFailure = Runnable { delegate?.didFailToSavePin() }
        )
    }

    override fun unlock(pin: String): Boolean {
        return pinManager.validate(pin)
    }

    override fun startAdapters() {
        keystoreSafeExecute.safeExecute(
                action = Runnable { authManager.safeLoad() },
                onSuccess = Runnable { delegate?.didStartedAdapters() }
        )
    }
}

package io.horizontalsystems.bankwallet.modules.pin

import android.content.Context
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat
import java.util.*

object PinModule {

    const val PIN_COUNT = 6

    interface IPinView {
        fun setTitle(title: Int)
        fun addPages(pages: List<PinPage>)
        fun showPage(index: Int)
        fun showErrorForPage(error: Int, pageIndex: Int)
        fun showError(error: Int)
        fun showPinWrong(pageIndex: Int)
        fun showCancel()
        fun fillCircles(length: Int, pageIndex: Int)
        fun hideToolbar()
        fun showFingerprintDialog(cryptoObject: FingerprintManagerCompat.CryptoObject)
        fun showLockView(until: Date)
        fun showAttemptsLeft(attempts: Int?, pageIndex: Int)
    }

    interface IPinViewDelegate {
        fun viewDidLoad()
        fun onEnter(pin: String, pageIndex: Int)
        fun onCancel()
        fun onDelete(pageIndex: Int)
        fun onBackPressed()
        fun onBiometricUnlock() {}
        fun showBiometricUnlock() {}
        fun resetPin()
    }

    interface IPinInteractor {
        fun set(pin: String?)
        fun validate(pin: String): Boolean
        fun save(pin: String)
        fun unlock(pin: String): Boolean
        fun startAdapters()
    }

    interface IPinInteractorDelegate {
        fun didSavePin()
        fun didFailToSavePin()
        fun didStartedAdapters()
    }

    fun startForSetPin(context: Context) {
        PinActivity.start(context, PinInteractionType.SET_PIN)
    }

    fun startForEditPin(context: Context) {
        PinActivity.start(context, PinInteractionType.EDIT_PIN)
    }

    fun startForUnlock() {
        PinActivity.startForUnlock()
    }
}

enum class PinInteractionType {
    SET_PIN,
    UNLOCK,
    EDIT_PIN
}

package io.horizontalsystems.bankwallet.modules.pin

import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat
import io.horizontalsystems.bankwallet.SingleLiveEvent
import io.horizontalsystems.bankwallet.core.IKeyStoreSafeExecute
import io.horizontalsystems.bankwallet.modules.pin.edit.EditPinModule
import io.horizontalsystems.bankwallet.modules.pin.set.SetPinModule
import io.horizontalsystems.bankwallet.modules.pin.unlock.UnlockPinModule
import java.util.*

class PinViewModel: ViewModel(), PinModule.IPinView, SetPinModule.ISetPinRouter, EditPinModule.IEditPinRouter, IKeyStoreSafeExecute, UnlockPinModule.IUnlockPinRouter {

    lateinit var delegate: PinModule.IPinViewDelegate
    val titleLiveDate = MutableLiveData<Int>()
    val addPagesEvent = MutableLiveData<List<PinPage>>()
    val showPageAtIndex = MutableLiveData<Int>()
    val showError = MutableLiveData<Int>()
    val showErrorForPage = MutableLiveData<Pair<Int, Int>>()
    val fillPinCircles = MutableLiveData<Pair<Int, Int>>()
    val navigateToMainLiveEvent = SingleLiveEvent<Unit>()
    val hideToolbar = SingleLiveEvent<Unit>()
    val dismissLiveEvent = SingleLiveEvent<Unit>()
    val showBackButton = SingleLiveEvent<Unit>()
    val showFingerprintInputLiveEvent = SingleLiveEvent<FingerprintManagerCompat.CryptoObject>()
    val resetCirclesWithShakeAndDelayForPage = SingleLiveEvent<Int>()
    val keyStoreSafeExecute = SingleLiveEvent<Triple<Runnable, Runnable?, Runnable?>>()
    val showAttemptsLeftError = MutableLiveData<Pair<Int, Int>>()
    val showLockedView = SingleLiveEvent<Date>()
    val closeApplicationLiveEvent = SingleLiveEvent<Unit>()

    fun init(interactionType: PinInteractionType) {
        when(interactionType) {
            PinInteractionType.SET_PIN -> SetPinModule.init(this, this, this)
            PinInteractionType.UNLOCK -> UnlockPinModule.init(this, this, this)
            PinInteractionType.EDIT_PIN -> EditPinModule.init(this, this, this)
        }
        delegate.viewDidLoad()
    }

    override fun setTitle(title: Int) {
        titleLiveDate.value = title
    }

    override fun hideToolbar() {
        hideToolbar.call()
    }

    override fun addPages(pages: List<PinPage>) {
        addPagesEvent.value = pages
    }

    override fun showPage(index: Int) {
        showPageAtIndex.value = index
    }

    override fun showErrorForPage(error: Int, pageIndex: Int) {
        showErrorForPage.value = Pair(error, pageIndex)
    }

    override fun showError(error: Int) {
        showError.value = error
    }

    override fun showPinWrong(pageIndex: Int) {
        resetCirclesWithShakeAndDelayForPage.value = pageIndex
    }

    override fun showFingerprintDialog(cryptoObject: FingerprintManagerCompat.CryptoObject) {
        showFingerprintInputLiveEvent.value = cryptoObject
    }

    override fun showCancel() {
        showBackButton.call()
    }

    override fun fillCircles(length: Int, pageIndex: Int) {
        fillPinCircles.value = Pair(length, pageIndex)
    }

    override fun navigateToMain() {
        navigateToMainLiveEvent.call()
    }

    override fun safeExecute(action: Runnable, onSuccess: Runnable?, onFailure: Runnable?) {
        keyStoreSafeExecute.value = Triple(action, onSuccess, onFailure)
    }

    override fun dismiss(didUnlock: Boolean) {
        if (didUnlock) {
            dismissLiveEvent.call()
        }
    }

    override fun closeApplication() {
        closeApplicationLiveEvent.call()
    }

    override fun dismiss() {
        dismissLiveEvent.call()
    }

    override fun showLockView(until: Date) {
        showLockedView.postValue(until)
    }

    override fun showAttemptsLeft(attempts: Int?, pageIndex: Int) {
        attempts?.let {
            showAttemptsLeftError.postValue(Pair(attempts, pageIndex))
        }
    }
}

package io.horizontalsystems.bankwallet.modules.receive

import android.app.Dialog
import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.design.widget.BottomSheetDialog
import android.support.design.widget.BottomSheetDialogFragment
import android.support.v4.app.FragmentActivity
import android.support.v4.content.ContextCompat
import android.view.Gravity
import android.view.WindowManager
import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode
import io.horizontalsystems.bankwallet.ui.extensions.AddressView
import io.horizontalsystems.bankwallet.viewHelpers.HudHelper
import io.horizontalsystems.bankwallet.viewHelpers.LayoutHelper
import io.horizontalsystems.bankwallet.viewHelpers.TextHelper
import android.support.v4.app.ShareCompat

class ReceiveFragment : BottomSheetDialogFragment() {

    private var mDialog: Dialog? = null

    private lateinit var viewModel: ReceiveViewModel

    private var coinCode: CoinCode? = null

    private var itemIndex = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        viewModel = ViewModelProviders.of(this).get(ReceiveViewModel::class.java)
        coinCode?.let { viewModel.init(it) } ?:  dismiss()
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        mDialog = activity?.let { BottomSheetDialog(it, R.style.BottomDialog) }
        mDialog?.setContentView(R.layout.fragment_bottom_sheet_receive)

        mDialog?.window?.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT)
        mDialog?.window?.setGravity(Gravity.BOTTOM)

        mDialog?.findViewById<Button>(R.id.btnShare)?.setOnClickListener { viewModel.delegate.onShareClick(itemIndex) }
        mDialog?.findViewById<AddressView>(R.id.addressView)?.setOnClickListener { viewModel.delegate.onAddressClick(itemIndex) }

        viewModel.showAddressesLiveData.observe(this, Observer { addresses ->
            addresses?.apply {
                if (addresses.isNotEmpty()) {
                    val address = addresses[itemIndex]
                    context?.let { ctx ->
                        val coinDrawable = ContextCompat.getDrawable(ctx, LayoutHelper.getCoinDrawableResource(address.coinCode))
                        mDialog?.findViewById<ImageView>(R.id.coinImg)?.setImageDrawable(coinDrawable)
                    }
                    mDialog?.findViewById<TextView>(R.id.txtTitle)?.text = getString(R.string.Deposit_Title, address.coinTitle)
                    mDialog?.findViewById<AddressView>(R.id.addressView)?.let { it.bind(address.address) }
                    mDialog?.findViewById<ImageView>(R.id.imgQrCode)?.setImageBitmap(TextHelper.getQrCodeBitmapFromAddress(address.address))
                }
            }
        })

        viewModel.showErrorLiveData.observe(this, Observer { error ->
            error?.let {
                HudHelper.showErrorMessage(it)
            }
            dismiss()
        })

        viewModel.showCopiedLiveEvent.observe(this, Observer {
            HudHelper.showSuccessMessage(R.string.Hud_Text_Copied)
        })

        viewModel.shareAddressLiveEvent.observe(this, Observer { address ->
            address?.let {
                ShareCompat.IntentBuilder.from(activity)
                        .setType("text/plain")
                        .setText(it)
                        .startChooser()
            }
        })

        return mDialog as Dialog
    }

    companion object {
        fun show(activity: FragmentActivity, coinCode: CoinCode) {
            val fragment = ReceiveFragment()
            fragment.coinCode = coinCode
            fragment.show(activity.supportFragmentManager, "receive_fragment")
        }
    }

}

package io.horizontalsystems.bankwallet.modules.receive

import io.horizontalsystems.bankwallet.core.IClipboardManager
import io.horizontalsystems.bankwallet.core.IWalletManager
import io.horizontalsystems.bankwallet.modules.receive.viewitems.AddressItem
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode

class ReceiveInteractor(
        private var coinCode: CoinCode?,
        private var walletManager: IWalletManager,
        private var clipboardManager: IClipboardManager) : ReceiveModule.IInteractor {

    var delegate: ReceiveModule.IInteractorDelegate? = null

    override fun getReceiveAddress() {
        val addresses = walletManager.wallets.filter { coinCode == null || it.coinCode == coinCode }.map {
            AddressItem(it.adapter.receiveAddress, it.coinCode, it.title)
        }

        if (addresses.isNotEmpty()) {
            delegate?.didReceiveAddresses(addresses)
        }
    }

    override fun copyToClipboard(coinAddress: String) {
        clipboardManager.copyText(coinAddress)
        delegate?.didCopyToClipboard()
    }

}

package io.horizontalsystems.bankwallet.modules.receive

import android.support.v4.app.FragmentActivity
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.modules.receive.viewitems.AddressItem
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode
import io.horizontalsystems.bankwallet.viewHelpers.TextHelper

object ReceiveModule {

    interface IView {
        fun showAddresses(addresses: List<AddressItem>)
        fun showError(error: Int)
        fun showCopied()
    }

    interface IViewDelegate {
        fun viewDidLoad()
        fun onShareClick(index: Int)
        fun onAddressClick(index: Int)
    }

    interface IInteractor {
        fun getReceiveAddress()
        fun copyToClipboard(coinAddress: String)
    }

    interface IInteractorDelegate {
        fun didReceiveAddresses(addresses: List<AddressItem>)
        fun didFailToReceiveAddress(exception: Exception)
        fun didCopyToClipboard()
    }

    interface IRouter{
        fun shareAddress(address: String)
    }

    fun init(coinCode: CoinCode?, view: ReceiveViewModel, router: IRouter) {
        val interactor = ReceiveInteractor(coinCode, App.walletManager, TextHelper)
        val presenter = ReceivePresenter(interactor, router)

        view.delegate = presenter
        presenter.view = view
        interactor.delegate = presenter
    }

    fun start(activity: FragmentActivity, coinCode: CoinCode) {
        ReceiveFragment.show(activity, coinCode)
    }

}

package io.horizontalsystems.bankwallet.modules.receive

import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.modules.receive.viewitems.AddressItem

class ReceivePresenter(
        private val interactor: ReceiveModule.IInteractor,
        private val router: ReceiveModule.IRouter) : ReceiveModule.IViewDelegate, ReceiveModule.IInteractorDelegate {

    var view: ReceiveModule.IView? = null
    private var receiveAddresses: List<AddressItem> = mutableListOf()

    override fun viewDidLoad() {
        interactor.getReceiveAddress()
    }

    override fun didReceiveAddresses(addresses: List<AddressItem>) {
        this.receiveAddresses = addresses
        view?.showAddresses(receiveAddresses)
    }

    override fun didFailToReceiveAddress(exception: Exception) {
        view?.showError(R.string.Error)
    }

    override fun onShareClick(index: Int) {
        router.shareAddress(receiveAddresses[index].address)
    }

    override fun onAddressClick(index: Int) {
        interactor.copyToClipboard(receiveAddresses[index].address)
    }

    override fun didCopyToClipboard() {
        view?.showCopied()
    }

}

package io.horizontalsystems.bankwallet.modules.receive

import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel
import io.horizontalsystems.bankwallet.SingleLiveEvent
import io.horizontalsystems.bankwallet.modules.receive.viewitems.AddressItem
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode

class ReceiveViewModel : ViewModel(), ReceiveModule.IView, ReceiveModule.IRouter {

    lateinit var delegate: ReceiveModule.IViewDelegate

    val showAddressesLiveData = MutableLiveData<List<AddressItem>>()
    val showErrorLiveData = MutableLiveData<Int>()
    val showCopiedLiveEvent = SingleLiveEvent<Unit>()
    val shareAddressLiveEvent = SingleLiveEvent<String>()

    fun init(coinCode: CoinCode) {
        ReceiveModule.init(coinCode, this, this)
        delegate.viewDidLoad()
    }

    override fun showAddresses(addresses: List<AddressItem>) {
        showAddressesLiveData.value = addresses
    }

    override fun showError(error: Int) {
        showErrorLiveData.value = error
    }

    override fun showCopied() {
        showCopiedLiveEvent.call()
    }

    override fun shareAddress(address: String) {
        shareAddressLiveEvent.value = address
    }
}

package io.horizontalsystems.bankwallet.modules.restore

import io.horizontalsystems.bankwallet.core.IKeyStoreSafeExecute
import io.horizontalsystems.bankwallet.core.ILocalStorage
import io.horizontalsystems.bankwallet.core.managers.AuthManager
import io.horizontalsystems.bankwallet.core.managers.WordsManager
import io.horizontalsystems.hdwalletkit.Mnemonic

class RestoreInteractor(
        private val authManager: AuthManager,
        private val wordsManager: WordsManager,
        private val localStorage: ILocalStorage,
        private val keystoreSafeExecute: IKeyStoreSafeExecute) : RestoreModule.IInteractor {

    var delegate: RestoreModule.IInteractorDelegate? = null

    override fun validate(words: List<String>) {
        try {
            wordsManager.validate(words)
            delegate?.didValidate()
        } catch (e: Mnemonic.MnemonicException) {
            delegate?.didFailToValidate(e)
        }
    }

    override fun restore(words: List<String>) {
        keystoreSafeExecute.safeExecute(
                action = Runnable {
                    authManager.login(words)
                },
                onSuccess = Runnable {
                    wordsManager.isBackedUp = true
                    localStorage.iUnderstand = true
                    delegate?.didRestore()
                },
                onFailure = Runnable {
                    delegate?.didFailToRestore(RestoreModule.RestoreFailedException())
                }
        )
    }

}

package io.horizontalsystems.bankwallet.modules.restore

import android.content.Context
import android.content.Intent
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.core.IKeyStoreSafeExecute

object RestoreModule {

    interface IView {
        fun showError(error: Int)
        fun showConfirmationDialog()
    }

    interface IViewDelegate {
        fun restoreDidClick(words: List<String>)
        fun didConfirm(words: List<String>)
    }

    interface IInteractor {
        fun restore(words: List<String>)
        fun validate(words: List<String>)
    }

    interface IInteractorDelegate {
        fun didRestore()
        fun didFailToRestore(exception: Exception)
        fun didFailToValidate(exception: Exception)
        fun didValidate()
    }

    interface IRouter {
        fun navigateToSetPin()
    }

    fun start(context: Context) {
        val intent = Intent(context, RestoreWalletActivity::class.java)
        context.startActivity(intent)
    }

    fun init(view: RestoreViewModel, router: IRouter, keystoreSafeExecute: IKeyStoreSafeExecute) {
        val interactor = RestoreInteractor(App.authManager, App.wordsManager, App.localStorage, keystoreSafeExecute)
        val presenter = RestorePresenter(interactor, router)

        view.delegate = presenter
        presenter.view = view
        interactor.delegate = presenter
    }

    class RestoreFailedException : Exception()

}

package io.horizontalsystems.bankwallet.modules.restore

import io.horizontalsystems.bankwallet.R

class RestorePresenter(
        private val interactor: RestoreModule.IInteractor,
        private val router: RestoreModule.IRouter) : RestoreModule.IViewDelegate, RestoreModule.IInteractorDelegate {

    var view: RestoreModule.IView? = null

    override fun restoreDidClick(words: List<String>) {
        interactor.validate(words)
    }

    override fun didFailToValidate(exception: Exception) {
        view?.showError(R.string.Restore_ValidationFailed)
    }

    override fun didFailToRestore(exception: Exception) {
        view?.showError(R.string.Restore_RestoreFailed)
    }

    override fun didValidate() {
        view?.showConfirmationDialog()
    }

    override fun didConfirm(words: List<String>) {
        interactor.restore(words)
    }

    override fun didRestore() {
        router.navigateToSetPin()
    }
}

package io.horizontalsystems.bankwallet.modules.restore

import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel
import io.horizontalsystems.bankwallet.SingleLiveEvent
import io.horizontalsystems.bankwallet.core.IKeyStoreSafeExecute

class RestoreViewModel : ViewModel(), RestoreModule.IView, RestoreModule.IRouter, IKeyStoreSafeExecute {

    lateinit var delegate: RestoreModule.IViewDelegate

    val errorLiveData = MutableLiveData<Int>()
    val navigateToSetPinLiveEvent = SingleLiveEvent<Void>()
    val keyStoreSafeExecute = SingleLiveEvent<Triple<Runnable, Runnable?, Runnable?>>()
    val showConfirmationDialogLiveEvent = SingleLiveEvent<Void>()

    fun init() {
        RestoreModule.init(this, this, this)
    }

    override fun showError(error: Int) {
        errorLiveData.value = error
    }

    override fun navigateToSetPin() {
        navigateToSetPinLiveEvent.call()
    }

    override fun safeExecute(action: Runnable, onSuccess: Runnable?, onFailure: Runnable?) {
        keyStoreSafeExecute.value = Triple(action, onSuccess, onFailure)
    }

    override fun showConfirmationDialog() {
        showConfirmationDialogLiveEvent.call()
    }
}

package io.horizontalsystems.bankwallet.modules.restore

import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v4.content.ContextCompat
import android.support.v7.widget.GridLayoutManager
import android.view.Menu
import android.view.MenuItem
import io.horizontalsystems.bankwallet.BaseActivity
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.core.Utils
import io.horizontalsystems.bankwallet.lib.EditTextViewHolder
import io.horizontalsystems.bankwallet.lib.WordsInputAdapter
import io.horizontalsystems.bankwallet.modules.pin.PinModule
import io.horizontalsystems.bankwallet.ui.dialogs.BottomConfirmAlert
import io.horizontalsystems.bankwallet.viewHelpers.HudHelper
import io.horizontalsystems.bankwallet.viewHelpers.LayoutHelper
import kotlinx.android.synthetic.main.activity_restore_wallet.*

class RestoreWalletActivity : BaseActivity(), BottomConfirmAlert.Listener {

    private lateinit var viewModel: RestoreViewModel

    private val words = MutableList(12, { "" })

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_restore_wallet)

        if (Utils.isUsingCustomKeyboard(this) ) {
            showCustomKeyboardAlert()
        }

        setSupportActionBar(toolbar)

        supportActionBar?.title = getString(R.string.Restore_Title)
        supportActionBar?.setDisplayHomeAsUpEnabled(true)
        supportActionBar?.setHomeAsUpIndicator(R.drawable.back)

        viewModel = ViewModelProviders.of(this).get(RestoreViewModel::class.java)
        viewModel.init()

        viewModel.errorLiveData.observe(this, Observer { errorId ->
            errorId?.let {
                HudHelper.showErrorMessage(it)
            }
        })

        viewModel.navigateToSetPinLiveEvent.observe(this, Observer {
            PinModule.startForSetPin(this)
        })

        viewModel.keyStoreSafeExecute.observe(this, Observer { triple ->
            triple?.let {
                val (action, onSuccess, onFailure) = it
                safeExecuteWithKeystore(action, onSuccess, onFailure)
            }
        })

        viewModel.showConfirmationDialogLiveEvent.observe(this, Observer {
            val confirmationList = mutableListOf(
                    R.string.Backup_Confirmation_Understand,
                    R.string.Backup_Confirmation_DeleteAppWarn,
                    R.string.Backup_Confirmation_LockAppWarn
            )
            BottomConfirmAlert.show(this, confirmationList, this)
        })

        recyclerInputs.adapter = WordsInputAdapter(object : EditTextViewHolder.WordsChangedListener {
            override fun set(position: Int, value: String) {
                words[position] = value
            }
        })
        recyclerInputs.layoutManager = GridLayoutManager(this, 2)
    }

    override fun onSupportNavigateUp(): Boolean {
        onBackPressed()
        return true
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menuInflater.inflate(R.menu.menu_restore_wallet, menu)
        LayoutHelper.tintMenuIcons(menu, ContextCompat.getColor(this, R.color.yellow_crypto))

        return super.onCreateOptionsMenu(menu)
    }

    override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
        R.id.action_done -> {
            viewModel.delegate.restoreDidClick(words)
            true
        }
        else -> {
            super.onOptionsItemSelected(item)
        }
    }

    override fun onConfirmationSuccess() {
        viewModel.delegate.didConfirm(words)
    }
}

package io.horizontalsystems.bankwallet.modules.send

import android.app.Dialog
import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.support.v4.app.FragmentActivity
import android.support.v4.content.ContextCompat
import android.support.v7.app.AlertDialog
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.ui.extensions.AddressView
import io.horizontalsystems.bankwallet.viewHelpers.LayoutHelper
import io.horizontalsystems.bankwallet.viewHelpers.ValueFormatter

class ConfirmationFragment : DialogFragment() {

    private lateinit var viewModel: SendViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        activity?.let {
            viewModel = ViewModelProviders.of(it).get(SendViewModel::class.java)
        }
    }

    override fun onCreateDialog(bundle: Bundle?): Dialog {
        val builder = activity?.let { AlertDialog.Builder(it, R.style.AlertDialog) }

        val rootView = View.inflate(context, R.layout.fragment_confirmation, null) as ViewGroup
        builder?.setView(rootView)

        viewModel.sendConfirmationViewItemLiveData.observe(this, Observer { viewItem ->
            viewItem?.let { sendConfirmationViewItem ->
                rootView.findViewById<TextView>(R.id.txtFiatAmount)?.text = sendConfirmationViewItem.currencyValue?.let {
                    ValueFormatter.format(it)
                }
                rootView.findViewById<TextView>(R.id.txtCryptoAmount)?.text = ValueFormatter.format(sendConfirmationViewItem.coinValue)
                rootView.findViewById<AddressView>(R.id.addressView)?.bind(sendConfirmationViewItem.address)
                rootView.findViewById<TextView>(R.id.txtFeeValue)?.text = sendConfirmationViewItem.feeInfo.getFormatted()
                rootView.findViewById<TextView>(R.id.txtTotalValue)?.text = sendConfirmationViewItem.totalInfo.getFormatted()
            }
        })

        viewModel.coinCodeLiveData.observe(this, Observer { coin ->
            coin?.let { coinCode ->
                context?.let {
                    val coinDrawable = ContextCompat.getDrawable(it, LayoutHelper.getCoinDrawableResource(coinCode))
                    rootView.findViewById<ImageView>(R.id.coinImg)?.setImageDrawable(coinDrawable)
                }
                rootView.findViewById<TextView>(R.id.txtTitle)?.text = getString(R.string.Send_Title, coinCode)
            }
        })

        rootView.findViewById<TextView>(R.id.buttonConfirm)?.let { buttonConfirm ->
            buttonConfirm.setOnClickListener {
                viewModel.delegate.onConfirmClicked()
                dismiss()
            }
        }

        val mDialog = builder?.create()
        return mDialog as Dialog
    }

    companion object {
        fun show(activity: FragmentActivity) {
            val fragment = ConfirmationFragment()
            fragment.show(activity.supportFragmentManager, "confirm_send_fragment")
        }
    }

}

package io.horizontalsystems.bankwallet.modules.send

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.KeyEvent
import io.horizontalsystems.bankwallet.R
import com.journeyapps.barcodescanner.CaptureManager
import com.journeyapps.barcodescanner.DecoratedBarcodeView
import kotlinx.android.synthetic.main.activity_qr_scanner.*

class QRScannerActivity : AppCompatActivity() {

    private var capture: CaptureManager? = null
    private var barcodeScannerView: DecoratedBarcodeView? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_qr_scanner)

        setSupportActionBar(toolbar)

        supportActionBar?.title = getString(R.string.Send_ScanQrCode)
        supportActionBar?.setDisplayHomeAsUpEnabled(true)
        supportActionBar?.setHomeAsUpIndicator(R.drawable.back)

        barcodeScannerView = findViewById(R.id.zxing_barcode_scanner)

        capture = CaptureManager(this, barcodeScannerView)
        capture?.initializeFromIntent(intent, savedInstanceState)
        capture?.decode()
    }

    override fun onResume() {
        super.onResume()
        capture?.onResume()
    }

    override fun onPause() {
        super.onPause()
        capture?.onPause()
    }

    override fun onDestroy() {
        super.onDestroy()
        capture?.onDestroy()
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        capture?.onSaveInstanceState(outState)
    }

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
        capture?.onRequestPermissionsResult(requestCode, permissions, grantResults)
    }

    override fun onSupportNavigateUp(): Boolean {
        onBackPressed()
        return true
    }

    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
        return barcodeScannerView?.onKeyDown(keyCode, event) ?: false || super.onKeyDown(keyCode, event)
    }

}

package io.horizontalsystems.bankwallet.modules.send

import android.annotation.SuppressLint
import android.app.Dialog
import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.support.design.widget.BottomSheetBehavior
import android.support.design.widget.BottomSheetDialog
import android.support.design.widget.BottomSheetDialogFragment
import android.support.v4.app.FragmentActivity
import android.support.v4.content.ContextCompat
import android.support.v7.widget.GridLayoutManager
import android.support.v7.widget.RecyclerView
import android.text.Editable
import android.text.TextUtils
import android.text.TextWatcher
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection
import android.widget.*
import com.google.zxing.integration.android.IntentIntegrator
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.ui.extensions.NumPadItem
import io.horizontalsystems.bankwallet.ui.extensions.NumPadItemType
import io.horizontalsystems.bankwallet.ui.extensions.NumPadItemsAdapter
import io.horizontalsystems.bankwallet.viewHelpers.HudHelper
import io.horizontalsystems.bankwallet.viewHelpers.LayoutHelper
import io.horizontalsystems.bankwallet.viewHelpers.ValueFormatter
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.subjects.PublishSubject
import java.math.BigDecimal
import java.math.RoundingMode
import java.util.concurrent.TimeUnit

class SendBottomSheetFragment : BottomSheetDialogFragment(), NumPadItemsAdapter.Listener {

    private lateinit var viewModel: SendViewModel
    private var disposable: Disposable? = null
    private var inputConnection: InputConnection? = null

    private var amountEditTxt: EditText? = null
    private var maxButton: Button? = null
    private val amountChangeSubject: PublishSubject<BigDecimal> = PublishSubject.create()

    private var coin: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        coin?.let { coin ->
            activity?.let {
                viewModel = ViewModelProviders.of(it).get(SendViewModel::class.java)
                viewModel.init(coin)
            }
        } ?: dismiss()

        disposable = amountChangeSubject.debounce(300, TimeUnit.MILLISECONDS)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe {
                    viewModel.delegate.onAmountChanged(it)
                }
    }

    @SuppressLint("ClickableViewAccessibility")
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {

        val mDialog = activity?.let { BottomSheetDialog(it, R.style.BottomDialog) }
        mDialog?.setContentView(R.layout.fragment_bottom_sheet_pay)

        mDialog?.setOnShowListener(object : DialogInterface.OnShowListener {
            override fun onShow(dialog: DialogInterface?) {
                val bottomSheet = mDialog.findViewById<View>(android.support.design.R.id.design_bottom_sheet)
                BottomSheetBehavior.from(bottomSheet).state = BottomSheetBehavior.STATE_EXPANDED
                BottomSheetBehavior.from(bottomSheet).isFitToContents = true
            }
        })

        val numpadAdapter = NumPadItemsAdapter(this, NumPadItemType.DOT, false)

        val numpadRecyclerView = mDialog?.findViewById<RecyclerView>(R.id.numPadItemsRecyclerView)
        val hintInfoTxt: TextView? = mDialog?.findViewById(R.id.txtHintInfo)
        val addressTxt: TextView? = mDialog?.findViewById(R.id.txtAddress)
        val addressErrorTxt: TextView? = mDialog?.findViewById(R.id.txtAddressError)
        val amountPrefixTxt: TextView? = mDialog?.findViewById(R.id.topAmountPrefix)
        val switchButton: ImageButton? = mDialog?.findViewById(R.id.btnSwitch)
        maxButton = mDialog?.findViewById(R.id.btnMax)
        val pasteButton: Button? = mDialog?.findViewById(R.id.btnPaste)
        val scanBarcodeButton: ImageButton? = mDialog?.findViewById(R.id.btnBarcodeScan)
        val deleteAddressButton: ImageButton? = mDialog?.findViewById(R.id.btnDeleteAddress)
        val feePrimaryTxt: TextView? = mDialog?.findViewById(R.id.txtFeePrimary)
        val feeSecondaryTxt: TextView? = mDialog?.findViewById(R.id.txtFeeSecondary)
        amountEditTxt = mDialog?.findViewById(R.id.editTxtAmount)
        val sendButton: Button? = mDialog?.findViewById(R.id.btnSend)

        amountEditTxt?.showSoftInputOnFocus = false
        inputConnection = amountEditTxt?.onCreateInputConnection(EditorInfo())
        sendButton?.isEnabled = false

        switchButton?.setOnClickListener { viewModel.delegate.onSwitchClicked() }
        scanBarcodeButton?.setOnClickListener { startScanner() }
        maxButton?.setOnClickListener { viewModel.delegate.onMaxClicked() }
        pasteButton?.setOnClickListener { viewModel.delegate.onPasteClicked() }
        deleteAddressButton?.setOnClickListener { viewModel.delegate.onDeleteClicked() }
        sendButton?.setOnClickListener { viewModel.delegate.onSendClicked() }

        amountEditTxt?.addTextChangedListener(textChangeListener)

        numpadRecyclerView?.adapter = numpadAdapter
        numpadRecyclerView?.layoutManager = GridLayoutManager(context, 3)

        //disable BottomSheet dragging in numpad area
        numpadRecyclerView?.setOnTouchListener { _, event ->
            when (event?.action) {
                MotionEvent.ACTION_DOWN, MotionEvent.ACTION_UP -> {
                    false
                }
                else -> {
                    true
                }
            }
        }

        viewModel.switchButtonEnabledLiveData.observe(this, Observer { enabled ->
            enabled?.let { switchButton?.isEnabled = it }
        })

        viewModel.coinTitleLiveData.observe(this, Observer { coinTitle ->
            coinTitle?.let {
                mDialog?.findViewById<TextView>(R.id.txtTitle)?.text = getString(R.string.Send_Title, it)
            }
        })

        viewModel.coinCodeLiveData.observe(this, Observer { coinCode ->
            coinCode?.let { code ->
                context?.let {
                    val coinDrawable = ContextCompat.getDrawable(it, LayoutHelper.getCoinDrawableResource(code))
                    mDialog?.findViewById<ImageView>(R.id.coinImg)?.setImageDrawable(coinDrawable)
                }
            }
        })

        viewModel.hintInfoLiveData.observe(this, Observer { hintInfo ->

            hintInfo?.let { hint ->

                if (hint is SendModule.HintInfo.ErrorInfo) {
                    hintInfoTxt?.let { it.setTextColor(it.resources.getColor(R.color.red_warning, null)) }
                } else {
                    activity?.theme?.let { theme ->
                        LayoutHelper.getAttr(R.attr.BottomDialogTextColor, theme)?.let {
                            hintInfoTxt?.setTextColor(it)
                        }
                    }
                }

                when (hint) {
                    is SendModule.HintInfo.Amount -> {
                        hintInfoTxt?.text = when (hint.amountInfo) {
                            is SendModule.AmountInfo.CoinValueInfo -> ValueFormatter.format(hint.amountInfo.coinValue)
                            is SendModule.AmountInfo.CurrencyValueInfo -> ValueFormatter.format(hint.amountInfo.currencyValue)
                        }
                    }
                    is SendModule.HintInfo.ErrorInfo -> {
                        when (hint.error) {
                            is SendModule.AmountError.InsufficientBalance -> {
                                val balanceAmount = when (hint.error.amountInfo) {
                                    is SendModule.AmountInfo.CoinValueInfo -> ValueFormatter.format(hint.error.amountInfo.coinValue)
                                    is SendModule.AmountInfo.CurrencyValueInfo -> ValueFormatter.format(hint.error.amountInfo.currencyValue)
                                }
                                hintInfoTxt?.text = hintInfoTxt?.context?.getString(R.string.Send_Error_BalanceAmount, balanceAmount)
                            }
                        }
                    }
                }
            }
        })

        viewModel.sendButtonEnabledLiveData.observe(this, Observer { enabled ->
            enabled?.let { sendButton?.isEnabled = it }
        })

        viewModel.amountInfoLiveData.observe(this, Observer { amountInfo ->
            var amountNumber = BigDecimal.ZERO
            when (amountInfo) {
                is SendModule.AmountInfo.CoinValueInfo -> {
                    amountPrefixTxt?.text = amountInfo.coinValue.coinCode
                    amountNumber = amountInfo.coinValue.value.setScale(8, RoundingMode.HALF_EVEN)
                }
                is SendModule.AmountInfo.CurrencyValueInfo -> {
                    amountPrefixTxt?.text = amountInfo.currencyValue.currency.symbol
                    amountNumber = amountInfo.currencyValue.value.setScale(2, RoundingMode.HALF_EVEN)
                }
            }

            if (amountNumber > BigDecimal.ZERO) {
                amountEditTxt?.setText(amountNumber.stripTrailingZeros().toPlainString())
                amountEditTxt?.setSelection(amountEditTxt?.text?.length ?: 0)
            }
        })

        viewModel.addressInfoLiveData.observe(this, Observer { addressInfo ->
            deleteAddressButton?.visibility = if (addressInfo == null) View.GONE else View.VISIBLE
            pasteButton?.visibility = if (addressInfo == null) View.VISIBLE else View.GONE
            scanBarcodeButton?.visibility = if (addressInfo == null) View.VISIBLE else View.GONE

            addressInfo?.let {
                when (it) {
                    is SendModule.AddressInfo.ValidAddressInfo -> {
                        addressTxt?.text = it.address
                        addressErrorTxt?.visibility = View.GONE
                    }
                    is SendModule.AddressInfo.InvalidAddressInfo -> {
                        addressTxt?.text = it.address
                        addressErrorTxt?.setText(R.string.Send_Error_IncorrectAddress)
                        addressErrorTxt?.visibility = View.VISIBLE
                    }
                }
            } ?: run {
                addressErrorTxt?.visibility = View.GONE
                addressTxt?.text = ""
            }
        })

        viewModel.primaryFeeAmountInfoLiveData.observe(this, Observer { amountInfo ->
            amountInfo?.let {
                feePrimaryTxt?.text = when (it) {
                    is SendModule.AmountInfo.CurrencyValueInfo -> ValueFormatter.format(it.currencyValue)
                    is SendModule.AmountInfo.CoinValueInfo -> ValueFormatter.format(it.coinValue)
                }
            }
        })

        viewModel.secondaryFeeAmountInfoLiveData.observe(this, Observer { amountInfo ->
            amountInfo?.let {
                feeSecondaryTxt?.text = when (it) {
                    is SendModule.AmountInfo.CurrencyValueInfo -> ValueFormatter.format(it.currencyValue)
                    is SendModule.AmountInfo.CoinValueInfo -> ValueFormatter.format(it.coinValue)
                }
            }
        })

        viewModel.dismissWithSuccessLiveEvent.observe(this, Observer {
            dismiss()
        })

        viewModel.errorLiveData.observe(this, Observer { error ->
            error?.let {
                HudHelper.showErrorMessage(R.string.Error)
            }
        })

        viewModel.showConfirmationLiveEvent.observe(this, Observer {
            activity?.let { ConfirmationFragment.show(it) }
        })

        viewModel.pasteButtonEnabledLiveData.observe(this, Observer { enabled ->
            enabled?.let { pasteButton?.isEnabled = it }
        })

        return mDialog as Dialog
    }

    override fun onItemClick(item: NumPadItem) {
        when (item.type) {
            NumPadItemType.NUMBER -> inputConnection?.commitText(item.number.toString(), 1)
            NumPadItemType.DELETE -> inputConnection?.deleteSurroundingText(1, 0)
            NumPadItemType.DOT -> {
                if (amountEditTxt?.text?.toString()?.contains(".") != true) {
                    inputConnection?.commitText(".", 1)
                }
            }
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
        super.onActivityResult(requestCode, resultCode, intent)
        val scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent)
        if (scanResult != null && !TextUtils.isEmpty(scanResult.contents)) {
            viewModel.delegate.onScanAddress(scanResult.contents)
        }
    }

    override fun onDestroy() {
        disposable?.dispose()
        super.onDestroy()
    }

    private fun startScanner() {
        val intentIntegrator = IntentIntegrator.forSupportFragment(this)
        intentIntegrator.captureActivity = QRScannerActivity::class.java
        intentIntegrator.setOrientationLocked(true)
        intentIntegrator.setPrompt("")
        intentIntegrator.setBeepEnabled(false)
        intentIntegrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE)
        intentIntegrator.initiateScan()
    }

    private val textChangeListener = object : TextWatcher {
        override fun afterTextChanged(s: Editable?) {
            val amountText = s?.toString() ?: ""
            var amountNumber = BigDecimal.ZERO
            try {
                amountNumber = amountText.toBigDecimal()
            } catch (e: NumberFormatException) {
                Log.e("SendFragment", "Exception", e)
            }

            maxButton?.visibility = if (amountText.isEmpty()) View.VISIBLE else View.GONE
            amountChangeSubject.onNext(amountNumber)
        }

        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
    }

    companion object {
        fun show(activity: FragmentActivity, coin: String) {
            val fragment = SendBottomSheetFragment()
            fragment.coin = coin
            fragment.show(activity.supportFragmentManager, "pay_fragment")
        }
    }
}

package io.horizontalsystems.bankwallet.modules.send

import io.horizontalsystems.bankwallet.core.*
import io.horizontalsystems.bankwallet.entities.*
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import java.math.BigDecimal
import java.math.RoundingMode

class SendInteractor(private val currencyManager: ICurrencyManager,
                     private val rateStorage: IRateStorage,
                     private val localStorage: ILocalStorage,
                     private val clipboardManager: IClipboardManager,
                     private val wallet: Wallet) : SendModule.IInteractor {

    sealed class SendError : Exception() {
        class NoAddress : SendError()
        class NoAmount : SendError()
    }

    override var defaultInputType: SendModule.InputType
        get() = localStorage.sendInputType ?: SendModule.InputType.COIN
        set(value) {
            localStorage.sendInputType = value
        }

    override val clipboardHasPrimaryClip: Boolean
        get() = clipboardManager.hasPrimaryClip

    var delegate: SendModule.IInteractorDelegate? = null

    override val coinTitle: String
        get() = wallet.title

    override val coinCode: CoinCode
        get() = wallet.coinCode

    override val addressFromClipboard: String?
        get() = clipboardManager.getCopiedText()

    private var rate: Rate? = null
    private val disposables = CompositeDisposable()

    override fun retrieveRate() {
        disposables.add(
                rateStorage.latestRateObservable(wallet.coinCode, currencyManager.baseCurrency.code)
                        .take(1)
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe {
                            rate = if (it.expired) null else it
                            delegate?.didRateRetrieve()
                        }
        )
    }

    override fun parsePaymentAddress(address: String): PaymentRequestAddress {
        return wallet.adapter.parsePaymentAddress(address)
    }

    override fun convertedAmountForInputType(inputType: SendModule.InputType, amount: BigDecimal): BigDecimal? {
        val rate = this.rate ?: return null

        return when (inputType) {
            SendModule.InputType.COIN -> amount.times(rate.value)
            SendModule.InputType.CURRENCY -> amount.divide(rate.value, 8, RoundingMode.DOWN).stripTrailingZeros()
        }
    }

    override fun getTotalBalanceMinusFee(inputType: SendModule.InputType, address: String?): BigDecimal {
        return try{
            val fee = wallet.adapter.fee(wallet.adapter.balance, address, false)
            when(inputType){
                SendModule.InputType.COIN -> wallet.adapter.balance.minus(fee)
                else -> {
                    val safeRate = rate?.value ?: BigDecimal.ZERO
                    var feeInCurrency = fee.multiply(safeRate)
                    feeInCurrency = feeInCurrency.setScale(2, RoundingMode.CEILING)
                    val balanceInCurrency = wallet.adapter.balance.multiply(safeRate)
                    balanceInCurrency.subtract(feeInCurrency)
                }
            }
        } catch (e:  Error.InsufficientAmount) {
            wallet.adapter.balance
        }
    }

    override fun stateForUserInput(input: SendModule.UserInput, senderPay: Boolean): SendModule.State {

        val coin = wallet.coinCode
        val adapter = wallet.adapter
        val baseCurrency = currencyManager.baseCurrency
        val rateValue = rate?.value

        val state = SendModule.State(input.inputType)

        state.address = input.address
        val address = input.address

        if (address != null) {
            try {
                adapter.validate(address)
            } catch (e: Exception) {
                state.addressError = SendModule.AddressError.InvalidAddress()
            }
        }

        when (input.inputType) {
            SendModule.InputType.COIN -> {
                state.coinValue = CoinValue(coin, input.amount)
                rateValue?.let {
                    state.currencyValue = CurrencyValue(baseCurrency, input.amount.times(it))
                }
            }
            SendModule.InputType.CURRENCY -> {
                state.currencyValue = CurrencyValue(baseCurrency, input.amount)
                rateValue?.let {
                    state.coinValue = CoinValue(coin, input.amount.divide(it, 8, RoundingMode.HALF_EVEN))
                }
            }
        }

        try {
            state.coinValue?.let { coinValue ->
                if ((state.coinValue?.value ?: BigDecimal.ZERO) > BigDecimal.ZERO) {
                    state.feeCoinValue = CoinValue(coin, adapter.fee(coinValue.value, input.address, senderPay))
                } else {
                    state.feeCoinValue = CoinValue(coin, BigDecimal.ZERO)
                }
            }
        } catch (e: Error.InsufficientAmount) {
            state.feeCoinValue = CoinValue(coin, e.fee)
            state.amountError = getAmountError(input.inputType, e.fee)
        }

        rateValue?.let {
            state.feeCoinValue?.let { feeCoinValue ->
                state.feeCurrencyValue = CurrencyValue(baseCurrency, feeCoinValue.value.times(rateValue))
            }
        }

        return state
    }

    private fun getAmountError(inputType: SendModule.InputType, fee: BigDecimal): SendModule.AmountError? {
        var balanceMinusFee = wallet.adapter.balance - fee
        if (balanceMinusFee < BigDecimal.ZERO) {
            balanceMinusFee = BigDecimal.ZERO
        }

        return when (inputType) {
            SendModule.InputType.COIN -> {
                SendModule.AmountError.InsufficientBalance(SendModule.AmountInfo.CoinValueInfo(CoinValue(wallet.coinCode, balanceMinusFee)))
            }
            SendModule.InputType.CURRENCY -> {
                rate?.value?.let {
                    val currencyBalanceMinusFee = balanceMinusFee * it
                    SendModule.AmountError.InsufficientBalance(SendModule.AmountInfo.CurrencyValueInfo(CurrencyValue(currencyManager.baseCurrency, currencyBalanceMinusFee)))
                }
            }
        }
    }

    override fun send(userInput: SendModule.UserInput) {
        val address = userInput.address
        if (address == null) {
            delegate?.didFailToSend(SendError.NoAddress())
            return
        }

        var computedAmount: BigDecimal? = null

        if (userInput.inputType == SendModule.InputType.COIN) {
            computedAmount = userInput.amount
        } else {
            val rateValue = rate?.value
            if (rateValue != null) {
                computedAmount = userInput.amount / rateValue
            }
        }

        val amount = computedAmount

        if (amount == null) {
            delegate?.didFailToSend(SendError.NoAmount())
            return
        }

        wallet.adapter.send(address, amount) { error ->
            when (error) {
                null -> delegate?.didSend()
                else -> delegate?.didFailToSend(error)
            }
        }
    }
}

package io.horizontalsystems.bankwallet.modules.send

import android.support.v4.app.FragmentActivity
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.entities.CoinValue
import io.horizontalsystems.bankwallet.entities.CurrencyValue
import io.horizontalsystems.bankwallet.entities.PaymentRequestAddress
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode
import io.horizontalsystems.bankwallet.viewHelpers.TextHelper
import io.horizontalsystems.bankwallet.viewHelpers.ValueFormatter
import java.math.BigDecimal

object SendModule {

    interface IView {
        fun setCoinTitle(coinTitle: String)
        fun setCoinCode(coinCode: CoinCode)

        fun setAmountInfo(amountInfo: AmountInfo?)
        fun setSwitchButtonEnabled(enabled: Boolean)
        fun setHintInfo(amountInfo: HintInfo?)

        fun setAddressInfo(addressInfo: AddressInfo?)

        fun setPrimaryFeeInfo(primaryFeeInfo: AmountInfo?)
        fun setSecondaryFeeInfo(secondaryFeeInfo: AmountInfo?)

        fun setSendButtonEnabled(sendButtonEnabled: Boolean)

        fun showConfirmation(viewItem: SendConfirmationViewItem)
        fun showError(error: Throwable)
        fun dismissWithSuccess()
        fun setPasteButtonState(enabled: Boolean)

    }

    interface IViewDelegate {
        fun onViewDidLoad()
        fun onAmountChanged(amount: BigDecimal)
        fun onSwitchClicked()
        fun onPasteClicked()
        fun onScanAddress(address: String)
        fun onDeleteClicked()
        fun onSendClicked()
        fun onConfirmClicked()
        fun onMaxClicked()
    }

    interface IInteractor {
        val clipboardHasPrimaryClip: Boolean
        val coinTitle: String
        val coinCode: CoinCode
        var defaultInputType: SendModule.InputType
        val addressFromClipboard: String?

        fun retrieveRate()
        fun parsePaymentAddress(address: String): PaymentRequestAddress
        fun convertedAmountForInputType(inputType: InputType, amount: BigDecimal): BigDecimal?
        fun stateForUserInput(input: UserInput, senderPay: Boolean = true): State

        fun send(userInput: UserInput)
        fun getTotalBalanceMinusFee(inputType: InputType, address: String?): BigDecimal
    }

    interface IInteractorDelegate {
        fun didRateRetrieve()
        fun didSend()
        fun didFailToSend(error: Throwable)
    }

    fun init(view: SendViewModel, coinCode: String) {
        val wallet = App.walletManager.wallets.first { it.coinCode == coinCode }
        val interactor = SendInteractor(App.currencyManager, App.rateStorage, App.localStorage, TextHelper, wallet)
        val presenter = SendPresenter(interactor, StateViewItemFactory(), UserInput())

        view.delegate = presenter
        presenter.view = view
        interactor.delegate = presenter
    }

    fun start(activity: FragmentActivity, coin: String) {
        SendBottomSheetFragment.show(activity, coin)
    }

    enum class InputType {
        COIN, CURRENCY
    }

    open class AmountError : Exception() {
        data class InsufficientBalance(val amountInfo: AmountInfo) : AmountError()
    }

    open class AddressError : Exception() {
        class InvalidAddress : AddressError()
    }

    sealed class HintInfo {
        data class Amount(val amountInfo: AmountInfo) : HintInfo()
        data class ErrorInfo(val error: AmountError) : HintInfo()
    }

    sealed class AddressInfo {
        class ValidAddressInfo(val address: String) : AddressInfo()
        class InvalidAddressInfo(val address: String, val error: Exception) : AddressInfo()
    }

    sealed class AmountInfo {
        data class CoinValueInfo(val coinValue: CoinValue) : AmountInfo()
        data class CurrencyValueInfo(val currencyValue: CurrencyValue) : AmountInfo()

        fun getFormatted(): String? = when (this) {
            is SendModule.AmountInfo.CoinValueInfo -> {
                ValueFormatter.format(this.coinValue)
            }
            is SendModule.AmountInfo.CurrencyValueInfo -> {
                ValueFormatter.formatSimple(this.currencyValue)
            }
        }
    }

    class UserInput {
        var inputType: InputType = InputType.COIN
        var amount: BigDecimal = BigDecimal.ZERO
        var address: String? = null
    }

    class State(var inputType: InputType) {
        var coinValue: CoinValue? = null
        var currencyValue: CurrencyValue? = null
        var amountError: AmountError? = null
        var address: String? = null
        var addressError: AddressError? = null
        var feeCoinValue: CoinValue? = null
        var feeCurrencyValue: CurrencyValue? = null
    }

    class StateViewItem {
        var amountInfo: AmountInfo? = null
        var switchButtonEnabled: Boolean = false
        var hintInfo: HintInfo? = null
        var addressInfo: AddressInfo? = null
        var primaryFeeInfo: AmountInfo? = null
        var secondaryFeeInfo: AmountInfo? = null
        var sendButtonEnabled: Boolean = false
    }

    class SendConfirmationViewItem(val coinValue: CoinValue, val address: String, val feeInfo: AmountInfo, val totalInfo: AmountInfo) {
        var currencyValue: CurrencyValue? = null
    }
}

package io.horizontalsystems.bankwallet.modules.send

import java.math.BigDecimal

class SendPresenter(
        private val interactor: SendModule.IInteractor,
        private val factory: StateViewItemFactory,
        private val userInput: SendModule.UserInput
) : SendModule.IViewDelegate, SendModule.IInteractorDelegate {

    var view: SendModule.IView? = null

    //
    // IViewDelegate
    //
    override fun onViewDidLoad() {
        userInput.inputType = interactor.defaultInputType

        val state = interactor.stateForUserInput(userInput)
        val viewItem = factory.viewItemForState(state)

        view?.setCoinTitle(interactor.coinTitle)
        view?.setCoinCode(interactor.coinCode)
        view?.setAmountInfo(viewItem.amountInfo)
        view?.setSwitchButtonEnabled(viewItem.switchButtonEnabled)
        view?.setHintInfo(viewItem.hintInfo)
        view?.setAddressInfo(viewItem.addressInfo)
        view?.setPrimaryFeeInfo(viewItem.primaryFeeInfo)
        view?.setSecondaryFeeInfo(viewItem.secondaryFeeInfo)
        view?.setSendButtonEnabled(viewItem.sendButtonEnabled)
        updatePasteButtonState()

        interactor.retrieveRate()
    }

    override fun onAmountChanged(amount: BigDecimal) {
        userInput.amount = amount

        val state = interactor.stateForUserInput(userInput)
        val viewItem = factory.viewItemForState(state)

        view?.setHintInfo(viewItem.hintInfo)
        view?.setPrimaryFeeInfo(viewItem.primaryFeeInfo)
        view?.setSecondaryFeeInfo(viewItem.secondaryFeeInfo)
        view?.setSendButtonEnabled(viewItem.sendButtonEnabled)
    }

    override fun onMaxClicked() {
        val totalBalanceMinusFee = interactor.getTotalBalanceMinusFee(userInput.inputType, userInput.address)
        userInput.amount = totalBalanceMinusFee

        val state = interactor.stateForUserInput(userInput)
        val viewItem = factory.viewItemForState(state)

        view?.setAmountInfo(viewItem.amountInfo)
    }

    override fun onSwitchClicked() {
        val convertedAmount = interactor.convertedAmountForInputType(userInput.inputType, userInput.amount)
                ?: return

        val newInputType = when (userInput.inputType) {
            SendModule.InputType.CURRENCY -> SendModule.InputType.COIN
            else -> SendModule.InputType.CURRENCY
        }

        userInput.amount = convertedAmount
        userInput.inputType = newInputType

        val state = interactor.stateForUserInput(userInput)
        val viewItem = factory.viewItemForState(state)

        view?.setAmountInfo(viewItem.amountInfo)
        view?.setHintInfo(viewItem.hintInfo)
        view?.setPrimaryFeeInfo(viewItem.primaryFeeInfo)
        view?.setSecondaryFeeInfo(viewItem.secondaryFeeInfo)

        interactor.defaultInputType = newInputType
    }

    override fun onPasteClicked() {
        interactor.addressFromClipboard?.let {
            onAddressEnter(it)
        }
    }

    override fun onScanAddress(address: String) {
        onAddressEnter(address)
    }

    override fun onDeleteClicked() {
        onAddressChange(null)
        updatePasteButtonState()
    }

    override fun onSendClicked() {
        val state = interactor.stateForUserInput(userInput)
        val viewItem = factory.confirmationViewItemForState(state) ?: return

        view?.showConfirmation(viewItem)
    }

    override fun onConfirmClicked() {
        interactor.send(userInput)
    }

    //
    // IInteractorDelegate
    //
    override fun didRateRetrieve() {
        val state = interactor.stateForUserInput(userInput)
        val viewItem = factory.viewItemForState(state)

        view?.setSwitchButtonEnabled(viewItem.switchButtonEnabled)
        view?.setHintInfo(viewItem.hintInfo)
        view?.setSecondaryFeeInfo(viewItem.secondaryFeeInfo)
    }

    override fun didSend() {
        view?.dismissWithSuccess()
    }

    override fun didFailToSend(error: Throwable) {
        view?.showError(error)
    }

    //
    // Private
    //
    private fun updatePasteButtonState() {
        view?.setPasteButtonState(interactor.clipboardHasPrimaryClip)
    }

    private fun onAddressEnter(address: String) {
        val paymentAddress = interactor.parsePaymentAddress(address)
        paymentAddress.amount?.let {
            userInput.amount = it
        }

        onAddressChange(paymentAddress.address)
    }

    private fun onAddressChange(address: String?) {
        userInput.address = address

        val state = interactor.stateForUserInput(userInput)
        val viewItem = factory.viewItemForState(state)

        view?.setAddressInfo(viewItem.addressInfo)
        view?.setAmountInfo(viewItem.amountInfo)
        view?.setPrimaryFeeInfo(viewItem.primaryFeeInfo)
        view?.setSecondaryFeeInfo(viewItem.secondaryFeeInfo)
        view?.setSendButtonEnabled(viewItem.sendButtonEnabled)
    }

}

package io.horizontalsystems.bankwallet.modules.send

import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel
import io.horizontalsystems.bankwallet.SingleLiveEvent
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode

class SendViewModel : ViewModel(), SendModule.IView {

    lateinit var delegate: SendModule.IViewDelegate

    val hintInfoLiveData = MutableLiveData<SendModule.HintInfo>()
    val sendButtonEnabledLiveData = MutableLiveData<Boolean>()
    val coinTitleLiveData = MutableLiveData<String>()
    val coinCodeLiveData = MutableLiveData<CoinCode>()
    val amountInfoLiveData = MutableLiveData<SendModule.AmountInfo>()
    val switchButtonEnabledLiveData = MutableLiveData<Boolean>()
    val addressInfoLiveData = MutableLiveData<SendModule.AddressInfo>()
    val dismissWithSuccessLiveEvent = SingleLiveEvent<Unit>()
    val primaryFeeAmountInfoLiveData = MutableLiveData<SendModule.AmountInfo>()
    val secondaryFeeAmountInfoLiveData = MutableLiveData<SendModule.AmountInfo>()
    val errorLiveData = MutableLiveData<Throwable>()
    val sendConfirmationViewItemLiveData = MutableLiveData<SendModule.SendConfirmationViewItem>()
    val showConfirmationLiveEvent = SingleLiveEvent<Unit>()
    val pasteButtonEnabledLiveData = MutableLiveData<Boolean>()

    fun init(coin: String) {
        hintInfoLiveData.value = null
        sendButtonEnabledLiveData.value = null
        coinTitleLiveData.value = null
        coinCodeLiveData.value = null
        amountInfoLiveData.value = null
        switchButtonEnabledLiveData.value = null
        addressInfoLiveData.value = null
        primaryFeeAmountInfoLiveData.value = null
        secondaryFeeAmountInfoLiveData.value = null
        errorLiveData.value = null
        sendConfirmationViewItemLiveData.value = null

        SendModule.init(this, coin)
        delegate.onViewDidLoad()
    }

    override fun setPasteButtonState(enabled: Boolean) {
        pasteButtonEnabledLiveData.value = enabled
    }

    override fun setCoinTitle(coinTitle: String) {
        coinTitleLiveData.value = coinTitle
    }

    override fun setCoinCode(coinCode: CoinCode) {
        coinCodeLiveData.value = coinCode
    }

    override fun setAmountInfo(amountInfo: SendModule.AmountInfo?) {
        amountInfoLiveData.value = amountInfo
    }

    override fun setSwitchButtonEnabled(enabled: Boolean) {
        switchButtonEnabledLiveData.value = enabled
    }

    override fun setHintInfo(amountInfo: SendModule.HintInfo?) {
        hintInfoLiveData.value = amountInfo
    }

    override fun setAddressInfo(addressInfo: SendModule.AddressInfo?) {
        addressInfoLiveData.value = addressInfo
    }

    override fun setPrimaryFeeInfo(primaryFeeInfo: SendModule.AmountInfo?) {
        primaryFeeAmountInfoLiveData.value = primaryFeeInfo
    }

    override fun setSecondaryFeeInfo(secondaryFeeInfo: SendModule.AmountInfo?) {
        secondaryFeeAmountInfoLiveData.value = secondaryFeeInfo
    }

    override fun setSendButtonEnabled(sendButtonEnabled: Boolean) {
        sendButtonEnabledLiveData.value = sendButtonEnabled
    }

    override fun showConfirmation(viewItem: SendModule.SendConfirmationViewItem) {
        sendConfirmationViewItemLiveData.value = viewItem
        showConfirmationLiveEvent.call()
    }

    override fun showError(error: Throwable) {
        errorLiveData.value = error
    }

    override fun dismissWithSuccess() {
        dismissWithSuccessLiveEvent.call()
    }

}

package io.horizontalsystems.bankwallet.modules.send

import io.horizontalsystems.bankwallet.entities.CoinValue
import io.horizontalsystems.bankwallet.entities.CurrencyValue
import java.math.BigDecimal

class StateViewItemFactory {

    fun viewItemForState(state: SendModule.State): SendModule.StateViewItem {
        val viewItem = SendModule.StateViewItem()

        when (state.inputType) {
            SendModule.InputType.COIN -> {

                viewItem.amountInfo = state.coinValue?.let { SendModule.AmountInfo.CoinValueInfo(it) }
                viewItem.primaryFeeInfo = state.feeCoinValue?.let { SendModule.AmountInfo.CoinValueInfo(it) }
                viewItem.secondaryFeeInfo = state.feeCurrencyValue?.let { SendModule.AmountInfo.CurrencyValueInfo(it) }
            }
            SendModule.InputType.CURRENCY -> {
                viewItem.amountInfo = state.currencyValue?.let { SendModule.AmountInfo.CurrencyValueInfo(it) }
                viewItem.primaryFeeInfo = state.feeCurrencyValue?.let { SendModule.AmountInfo.CurrencyValueInfo(it) }
                viewItem.secondaryFeeInfo = state.feeCoinValue?.let { SendModule.AmountInfo.CoinValueInfo(it) }
            }
        }

        viewItem.switchButtonEnabled = state.currencyValue != null

        val amountError = state.amountError

        if (amountError != null) {
            viewItem.hintInfo = SendModule.HintInfo.ErrorInfo(amountError)
        } else {
            when (state.inputType) {
                SendModule.InputType.COIN -> {
                    viewItem.hintInfo = state.currencyValue?.let { SendModule.HintInfo.Amount(SendModule.AmountInfo.CurrencyValueInfo(it)) }
                }
                SendModule.InputType.CURRENCY -> {
                    viewItem.hintInfo = state.coinValue?.let { SendModule.HintInfo.Amount(SendModule.AmountInfo.CoinValueInfo(it)) }
                }
            }
        }

        val address = state.address

        if (address != null) {
            val addressError = state.addressError
            if (addressError != null) {
                viewItem.addressInfo = SendModule.AddressInfo.InvalidAddressInfo(address, addressError)
            } else {
                viewItem.addressInfo = SendModule.AddressInfo.ValidAddressInfo(address)
            }
        }

        val zeroAmount = state.coinValue?.let { it.value.compareTo(BigDecimal.ZERO) == 0 } ?: true
        viewItem.sendButtonEnabled = !zeroAmount && state.address != null && state.amountError == null && state.addressError == null

        return viewItem
    }

    fun confirmationViewItemForState(state: SendModule.State) : SendModule.SendConfirmationViewItem? {
        val coinValue = state.coinValue ?: return null
        val address = state.address ?: return null

        var stateFeeInfo: SendModule.AmountInfo? = null
        var stateTotalInfo: SendModule.AmountInfo? = null

        val feeCurrencyValue = state.feeCurrencyValue

        if (feeCurrencyValue != null) {
            stateFeeInfo = SendModule.AmountInfo.CurrencyValueInfo(feeCurrencyValue)

            val currencyValue = state.currencyValue

            if (currencyValue != null) {
                stateTotalInfo = SendModule.AmountInfo.CurrencyValueInfo(CurrencyValue(currencyValue.currency, currencyValue.value + feeCurrencyValue.value))
            }
        } else {
            val feeCoinValue = state.feeCoinValue
            if (feeCoinValue != null) {
                stateFeeInfo = SendModule.AmountInfo.CoinValueInfo(feeCoinValue)
                stateTotalInfo = SendModule.AmountInfo.CoinValueInfo(CoinValue(coinValue. coinCode, coinValue.value + feeCoinValue.value))
            }
        }

        val feeInfo = stateFeeInfo ?: return null
        val totalInfo = stateTotalInfo ?: return null

        val viewItem = SendModule.SendConfirmationViewItem(coinValue, address, feeInfo, totalInfo)
        viewItem.currencyValue = state.currencyValue

        return viewItem
    }

}

package io.horizontalsystems.bankwallet.modules.settings

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.MenuItem
import io.horizontalsystems.bankwallet.BaseActivity
import io.horizontalsystems.bankwallet.R
import kotlinx.android.synthetic.main.activity_about_settings.*

class AboutSettingsActivity: BaseActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_about_settings)

        setSupportActionBar(toolbar)
        supportActionBar?.setDisplayHomeAsUpEnabled(true)
        supportActionBar?.setHomeAsUpIndicator(R.drawable.back)
        supportActionBar?.title = getString(R.string.SettingsAbout_Title)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            android.R.id.home -> {
                onBackPressed()
                return true
            }
        }
        return super.onOptionsItemSelected(item)
    }

    companion object {
        fun start(context: Activity) {
            val intent = Intent(context, AboutSettingsActivity::class.java)
            context.startActivity(intent)
        }
    }
}

package io.horizontalsystems.bankwallet.modules.settings

import android.content.Context
import android.graphics.drawable.Drawable
import android.support.constraint.ConstraintLayout
import android.util.AttributeSet
import android.view.View
import android.widget.CompoundButton
import android.widget.ImageView
import android.widget.Switch
import android.widget.TextView
import io.horizontalsystems.bankwallet.R

class SettingsItemView : ConstraintLayout {

    private var attrTitle: String? = null
    private var attrIcon: Drawable? = null

    private lateinit var settingIconImageView: ImageView
    private lateinit var titleTextView: TextView
    private lateinit var selectedValueTextView: TextView
    private lateinit var switch: Switch
    private lateinit var checkMarkImageView: ImageView
    private lateinit var badgeImageView: ImageView

    var titleTextColor: Int? = null
        set(colorId) {
            colorId?.let {
                titleTextView.setTextColor(resources.getColor(colorId, null))
            }
        }

    var switchIsChecked: Boolean = false
        set(isChecked) {
            switchOnCheckedChangeListener = null
            switch.isChecked = isChecked
            switch.visibility = View.VISIBLE
            invalidate()
        }

    var switchOnCheckedChangeListener: CompoundButton.OnCheckedChangeListener? = null
        set(value) {
            switch.setOnCheckedChangeListener(value)
            invalidate()
        }

    fun switchToggle() {
        switch.toggle()
    }

    var selectedValue: String? = null
        set(value) {
            selectedValueTextView.visibility = if (value != null) View.VISIBLE else View.GONE
            selectedValueTextView.text = value
            invalidate()
        }

    var checkMarkIsShown: Boolean = false
        set(isShown) {
            checkMarkImageView.visibility = if (isShown) View.VISIBLE else View.GONE
            invalidate()
        }

    var badge: Drawable? = null
        set(badge) {
            badgeImageView.visibility = if (badge != null) View.VISIBLE else View.GONE
            badgeImageView.setImageDrawable(badge)
            invalidate()
        }

    constructor(context: Context) : super(context) {
        initializeViews()
    }

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
        initializeViews()
        loadAttributes(attrs)
    }

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        initializeViews()
        loadAttributes(attrs)
    }

    private fun initializeViews() {
        ConstraintLayout.inflate(context, R.layout.view_setting_item, this)
    }

    private fun loadAttributes(attrs: AttributeSet) {
        val ta = context.obtainStyledAttributes(attrs, R.styleable.SettingsItemView, 0, 0)
        try {
            attrTitle = ta.getString(R.styleable.SettingsItemView_setting_title)
            attrIcon = ta.getDrawable(R.styleable.SettingsItemView_setting_icon)
        } finally {
            ta.recycle()
        }
    }

    override fun onFinishInflate() {
        super.onFinishInflate()

        settingIconImageView = findViewById(R.id.setting_icon)
        titleTextView = findViewById(R.id.setting_title)
        selectedValueTextView = findViewById(R.id.setting_value)
        switch = findViewById(R.id.setting_switch)
        switch.isClickable = false
        checkMarkImageView = findViewById(R.id.setting_check_mark)
        badgeImageView = findViewById(R.id.setting_badge)

        settingIconImageView.apply {
            visibility = if (attrIcon == null) View.GONE else View.VISIBLE
            setImageDrawable(attrIcon)
        }

        titleTextView.text = attrTitle

    }

}

package io.horizontalsystems.bankwallet.modules.transactions

class LastBlockHeightDataSource {

    private val lastBlockHeights = mutableMapOf<CoinCode, Int>()
    private val thresholds = mutableMapOf<CoinCode, Int>()

    fun setLastBlockHeight(lastBlockHeight: Int, coinCode: CoinCode) {
        lastBlockHeights[coinCode] = lastBlockHeight
    }

    fun getLastBlockHeight(coinCode: CoinCode): Int? =
            lastBlockHeights[coinCode]

    fun setConfirmationThreshold(threshold: Int, coinCode: CoinCode) {
        thresholds[coinCode] = threshold
    }

    fun getConfirmationThreshold(coinCode: CoinCode): Int? =
            thresholds[coinCode]

}

package io.horizontalsystems.bankwallet.modules.transactions

import io.horizontalsystems.bankwallet.entities.TransactionRecord
import java.util.concurrent.CopyOnWriteArrayList

class Pool(val state: State) {

    class State(val coinCode: CoinCode) {
        val records: MutableList<TransactionRecord> = CopyOnWriteArrayList<TransactionRecord>()

        var firstUnusedIndex = 0
        var allLoaded = false

        val unusedRecords: List<TransactionRecord>
            get() = when {
                records.isEmpty() -> listOf()
                else -> records.subList(firstUnusedIndex, records.size)
            }

        fun add(records: List<TransactionRecord>) {
            this.records.addAll(records)
        }

        fun indexOf(record: TransactionRecord): Int {
            return records.indexOf(record)
        }

        fun insertIndexOf(record: TransactionRecord): Int {
            return records.indexOfFirst {
                it.timestamp < record.timestamp
            }
        }

        fun setRecord(index: Int, record: TransactionRecord) {
            records[index] = record
        }

        fun insertRecord(insertIndex: Int, record: TransactionRecord) {
            records.add(insertIndex, record)
        }

    }

    val records: MutableList<TransactionRecord>
        get() = state.records

    val allShown: Boolean
        get() = state.allLoaded && state.unusedRecords.isEmpty()

    val unusedRecords: List<TransactionRecord>
        get() = state.unusedRecords

    val coinCode: CoinCode
        get() = state.coinCode

    fun increaseFirstUnusedIndex() {
        state.firstUnusedIndex++
    }

    fun resetFirstUnusedIndex() {
        state.firstUnusedIndex = 0
    }

    fun getFetchData(limit: Int): TransactionsModule.FetchData? {
        if (state.allLoaded) return null

        val unusedRecordsSize = state.unusedRecords.size
        if (unusedRecordsSize > limit) return null

        val hashFrom = state.records.lastOrNull()?.transactionHash
        val fetchLimit = limit + 1 - unusedRecordsSize

        return TransactionsModule.FetchData(state.coinCode, hashFrom, fetchLimit)
    }

    fun add(transactionRecords: List<TransactionRecord>) {
        if (transactionRecords.isEmpty()) {
            state.allLoaded = true
        } else {
            state.add(transactionRecords)
        }
    }

    fun handleUpdatedRecord(record: TransactionRecord): HandleResult {
        val updatedRecordIndex = state.indexOf(record)

        if (updatedRecordIndex != -1) {
            state.setRecord(updatedRecordIndex, record)

            if (updatedRecordIndex < state.firstUnusedIndex) {
                return HandleResult.UPDATED
            } else {
                return HandleResult.IGNORED
            }
        }

        val insertIndex = state.insertIndexOf(record)

        if (insertIndex != -1) {
            state.insertRecord(insertIndex, record)

            if (insertIndex < state.firstUnusedIndex) {
                increaseFirstUnusedIndex()
                return HandleResult.INSERTED
            } else if (insertIndex == 0) {
                return HandleResult.NEW_DATA
            } else {
                return HandleResult.IGNORED
            }
        } else if (state.allLoaded && unusedRecords.isEmpty()) {
            state.add(listOf(record))
            return HandleResult.NEW_DATA
        } else {
            return HandleResult.IGNORED
        }
    }

    enum class HandleResult {
        UPDATED, INSERTED, IGNORED, NEW_DATA
    }

}

package io.horizontalsystems.bankwallet.modules.transactions

class PoolRepo {

    val activePools: List<Pool>
        get() = activePoolCoinCodes.mapNotNull { pools[it] }

    val allPools: List<Pool>
        get() = pools.values.toList()

    private var pools = mutableMapOf<CoinCode, Pool>()
    private var activePoolCoinCodes = listOf<CoinCode>()

    fun activatePools(coinCodes: List<CoinCode>) {
        coinCodes.forEach { coinCode ->
            if (!pools.containsKey(coinCode)) {
                pools[coinCode] = Pool(Pool.State(coinCode))
            }
        }

        this.activePoolCoinCodes = coinCodes
    }

    fun getPool(coinCode: CoinCode): Pool? {
        return pools[coinCode]
    }

    fun isPoolActiveByCoinCode(coinCode: CoinCode): Boolean {
        return activePoolCoinCodes.contains(coinCode)
    }

}

package io.horizontalsystems.bankwallet.modules.transactions

class TransactionFilterItem(val coinCode: CoinCode?, val name: String)

package io.horizontalsystems.bankwallet.modules.transactions

import io.horizontalsystems.bankwallet.entities.TransactionItem
import io.horizontalsystems.bankwallet.entities.TransactionRecord
import java.util.concurrent.CopyOnWriteArrayList

class TransactionItemDataSource {
    val count
        get() = items.size

    private val items = CopyOnWriteArrayList<TransactionItem>()

    fun clear() {
        items.clear()
    }

    fun add(items: List<TransactionItem>) {
        this.items.addAll(items)
    }

    fun itemForIndex(index: Int): TransactionItem = items[index]

    fun itemIndexesForTimestamp(coinCode: CoinCode, timestamp: Long): List<Int> {
        val indexes = mutableListOf<Int>()

        items.forEachIndexed { index, transactionItem ->
            if (transactionItem.coinCode == coinCode && transactionItem.record.timestamp == timestamp) {
                indexes.add(index)
            }
        }

        return indexes
    }

    fun handleModifiedItems(updatedItems: List<TransactionItem>, insertedItems: List<TransactionItem>) {
        val tmpList = items.toMutableList()
        tmpList.removeAll(updatedItems)
        tmpList.addAll(updatedItems)
        tmpList.addAll(insertedItems)
        tmpList.sortByDescending { it.record.timestamp }

        items.clear()
        items.addAll(tmpList)
    }

    fun shouldInsertRecord(record: TransactionRecord): Boolean {
        return items.lastOrNull()?.record?.let { it.timestamp < record.timestamp } ?: true
    }

}

package io.horizontalsystems.bankwallet.modules.transactions

import io.horizontalsystems.bankwallet.entities.TransactionItem
import io.horizontalsystems.bankwallet.entities.TransactionRecord

class TransactionItemFactory {

    fun createTransactionItem(coinCode: CoinCode, record: TransactionRecord): TransactionItem {
        return TransactionItem(coinCode, record)
    }

}

package io.horizontalsystems.bankwallet.modules.transactions

import io.horizontalsystems.bankwallet.entities.Currency
import io.horizontalsystems.bankwallet.entities.CurrencyValue
import java.math.BigDecimal

class TransactionMetadataDataSource {

    private val lastBlockHeights = mutableMapOf<CoinCode, Int>()
    private val thresholds = mutableMapOf<CoinCode, Int>()
    private val rates = mutableMapOf<CoinCode, MutableMap<Long, CurrencyValue>>()

    fun setLastBlockHeight(lastBlockHeight: Int, coinCode: CoinCode) {
        lastBlockHeights[coinCode] = lastBlockHeight
    }

    fun getLastBlockHeight(coinCode: CoinCode): Int? {
        return lastBlockHeights[coinCode]
    }

    fun setConfirmationThreshold(confirmationThreshold: Int, coinCode: CoinCode) {
        thresholds[coinCode] = confirmationThreshold
    }

    fun getConfirmationThreshold(coinCode: CoinCode): Int? =
            thresholds[coinCode]

    fun setRate(rateValue: BigDecimal, coinCode: CoinCode, currency: Currency, timestamp: Long) {
        if (!rates.containsKey(coinCode)) {
            rates[coinCode] = mutableMapOf()
        }

        rates[coinCode]?.set(timestamp, CurrencyValue(currency, rateValue))
    }

    fun getRate(coinCode: String, timestamp: Long): CurrencyValue? =
            rates[coinCode]?.get(timestamp)

    fun clearRates() {
        rates.clear()
    }

}

package io.horizontalsystems.bankwallet.modules.transactions

import io.horizontalsystems.bankwallet.entities.TransactionItem
import io.horizontalsystems.bankwallet.entities.TransactionRecord
import io.horizontalsystems.bankwallet.modules.transactions.TransactionsModule.FetchData

class TransactionRecordDataSource(
        private val poolRepo: PoolRepo,
        private val itemsDataSource: TransactionItemDataSource,
        private val factory: TransactionItemFactory,
        private val limit: Int = 10) {

    val itemsCount
        get() = itemsDataSource.count

    val allShown
        get() = poolRepo.activePools.all { it.allShown }

    val allRecords: Map<CoinCode, List<TransactionRecord>>
        get() = poolRepo.activePools.map {
            Pair(it.coinCode, it.records)
        }.toMap()

    fun itemForIndex(index: Int): TransactionItem =
            itemsDataSource.itemForIndex(index)

    fun itemIndexesForTimestamp(coinCode: CoinCode, timestamp: Long): List<Int> =
            itemsDataSource.itemIndexesForTimestamp(coinCode, timestamp)

    fun getFetchDataList(): List<FetchData> = poolRepo.activePools.mapNotNull {
        it.getFetchData(limit)
    }

    fun handleNextRecords(records: Map<CoinCode, List<TransactionRecord>>) {
        records.forEach { (coinCode, transactionRecords) ->
            poolRepo.getPool(coinCode)?.add(transactionRecords)
        }
    }

    fun handleUpdatedRecords(records: List<TransactionRecord>, coinCode: CoinCode): Boolean {
        val pool = poolRepo.getPool(coinCode) ?: return false

        val updatedRecords = mutableListOf<TransactionRecord>()
        val insertedRecords = mutableListOf<TransactionRecord>()
        var newData = false

        records.forEach {
            when (pool.handleUpdatedRecord(it)) {
                Pool.HandleResult.UPDATED -> updatedRecords.add(it)
                Pool.HandleResult.INSERTED -> insertedRecords.add(it)
                Pool.HandleResult.NEW_DATA -> {
                    if (itemsDataSource.shouldInsertRecord(it)) {
                        insertedRecords.add(it)
                        pool.increaseFirstUnusedIndex()
                    }
                    newData = true
                }
                Pool.HandleResult.IGNORED -> {
                }
            }
        }

        if (!poolRepo.isPoolActiveByCoinCode(coinCode)) return false

        if (updatedRecords.isEmpty() && insertedRecords.isEmpty()) return newData

        val updatedItems = updatedRecords.map { factory.createTransactionItem(coinCode, it) }
        val insertedItems = insertedRecords.map { factory.createTransactionItem(coinCode, it) }

        itemsDataSource.handleModifiedItems(updatedItems, insertedItems)

        return true
    }

    fun increasePage(): Int {
        val unusedItems = mutableListOf<TransactionItem>()

        poolRepo.activePools.forEach { pool ->
            unusedItems.addAll(pool.unusedRecords.map { record ->
                factory.createTransactionItem(pool.coinCode, record)
            })
        }

        if (unusedItems.isEmpty()) return 0

        unusedItems.sortByDescending { it.record.timestamp }

        val usedItems = unusedItems.take(limit)

        itemsDataSource.add(usedItems)

        usedItems.forEach {
            poolRepo.getPool(it.coinCode)?.increaseFirstUnusedIndex()
        }

        return usedItems.size
    }

    fun setCoinCodes(coinCodes: List<CoinCode>) {
        poolRepo.allPools.forEach {
            it.resetFirstUnusedIndex()
        }
        poolRepo.activatePools(coinCodes)
        itemsDataSource.clear()
    }

}

package io.horizontalsystems.bankwallet.modules.transactions

import io.horizontalsystems.bankwallet.entities.CoinValue
import io.horizontalsystems.bankwallet.entities.CurrencyValue
import java.util.*

data class TransactionRecordViewItem(
        val hash: String,
        val adapterId: String,
        val amount: CoinValue,
        val fee: CoinValue,
        val from: String?,
        val to: String?,
        val incoming: Boolean,
        val blockHeight: Long?,
        val date: Date?,
        val status: TransactionStatus,
        var currencyAmount: CurrencyValue? = null,
        var exchangeRate: Double? = null
) {

    override fun equals(other: Any?): Boolean {
        if (other is TransactionRecordViewItem) {
            return hash == other.hash
                    && adapterId == other.adapterId
                    && amount == other.amount
                    && fee == other.fee
                    && from == other.from
                    && to == other.to
                    && incoming == other.incoming
                    && date == other.date
                    && currencyAmount == other.currencyAmount
                    && exchangeRate == other.exchangeRate
        }

        return super.equals(other)
    }

    override fun hashCode(): Int {
        var result = hash.hashCode()
        result = 31 * result + adapterId.hashCode()
        result = 31 * result + amount.hashCode()
        result = 31 * result + fee.hashCode()
        result = 31 * result + (from?.hashCode() ?: 0)
        result = 31 * result + (to?.hashCode() ?: 0)
        result = 31 * result + incoming.hashCode()
        result = 31 * result + (date?.hashCode() ?: 0)
        result = 31 * result + (currencyAmount?.hashCode() ?: 0)
        result = 31 * result + (exchangeRate?.hashCode() ?: 0)
        return result
    }

}

package io.horizontalsystems.bankwallet.modules.transactions

import android.content.Context
import android.support.constraint.ConstraintLayout
import android.util.AttributeSet
import android.view.View
import io.horizontalsystems.bankwallet.R
import kotlinx.android.synthetic.main.view_transaction_status.view.*

open class TransactionStatusWithTimeView : ConstraintLayout {

    constructor(context: Context) : super(context) {
        initializeViews()
    }

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
        initializeViews()
    }

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        initializeViews()
    }

    private fun initializeViews() {
        ConstraintLayout.inflate(context, R.layout.view_transaction_status, this)
    }

    fun bind(transactionStatus: TransactionStatus, time: String?) {
        txTime.text = time

        txTime.visibility = View.VISIBLE
        progressBarWrapper.visibility = View.GONE
        pendingIcon.visibility = View.GONE
        completedIcon.visibility = View.GONE
        pendingProgressBarWrapper.visibility = View.GONE
        when (transactionStatus) {
            is TransactionStatus.Completed -> {
                completedIcon.visibility = View.VISIBLE
            }
            is TransactionStatus.Processing -> {
                progressBarWrapper.visibility = View.VISIBLE
                fillProgress(transactionStatus)
            }
            else -> {
                txTime.visibility = View.GONE
                pendingIcon.visibility = View.VISIBLE
                pendingProgressBarWrapper.visibility = View.VISIBLE
            }
        }
        invalidate()
    }

    private fun fillProgress(transactionStatus: TransactionStatus.Processing) {
        val grey20Bar = R.drawable.status_progress_bar_grey_20_small
        val solidGreyBar = R.drawable.status_progress_bar_grey_small

        progressBar1.setImageResource(if (transactionStatus.progress >= 1) solidGreyBar else grey20Bar)
        progressBar2.setImageResource(if (transactionStatus.progress >= 2) solidGreyBar else grey20Bar)
        progressBar3.setImageResource(if (transactionStatus.progress >= 3) solidGreyBar else grey20Bar)
        progressBar4.setImageResource(if (transactionStatus.progress >= 4) solidGreyBar else grey20Bar)
        progressBar5.setImageResource(if (transactionStatus.progress >= 5) solidGreyBar else grey20Bar)
        progressBar6.setImageResource(if (transactionStatus.progress >= 6) solidGreyBar else grey20Bar)
    }

}

package io.horizontalsystems.bankwallet.modules.transactions

import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.annotation.NonNull
import android.support.design.widget.BottomSheetBehavior
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.core.setOnSingleClickListener
import io.horizontalsystems.bankwallet.modules.fulltransactioninfo.FullTransactionInfoModule
import io.horizontalsystems.bankwallet.modules.main.MainActivity
import io.horizontalsystems.bankwallet.modules.transactions.transactionInfo.TransactionInfoViewModel
import io.horizontalsystems.bankwallet.viewHelpers.DateHelper
import io.horizontalsystems.bankwallet.viewHelpers.HudHelper
import io.horizontalsystems.bankwallet.viewHelpers.ValueFormatter
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.fragment_transactions.*
import kotlinx.android.synthetic.main.transaction_info_bottom_sheet.*
import kotlinx.android.synthetic.main.view_holder_filter.*
import kotlinx.android.synthetic.main.view_holder_transaction.*

class TransactionsFragment : android.support.v4.app.Fragment(), TransactionsAdapter.Listener, FilterAdapter.Listener {

    private lateinit var viewModel: TransactionsViewModel
    private lateinit var transInfoViewModel: TransactionInfoViewModel
    private val transactionsAdapter = TransactionsAdapter(this)
    private val filterAdapter = FilterAdapter(this)
    private var bottomSheetBehavior: BottomSheetBehavior<View>? = null

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_transactions, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        viewModel = ViewModelProviders.of(this).get(TransactionsViewModel::class.java)
        viewModel.init()

        transactionsAdapter.viewModel = viewModel
        toolbar.setTitle(R.string.Transactions_Title)

        recyclerTransactions.setHasFixedSize(true)
        recyclerTransactions.adapter = transactionsAdapter
        recyclerTransactions.layoutManager = LinearLayoutManager(context)
        recyclerTransactions.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                filterAdapter.filterChangeable = newState == SCROLL_STATE_IDLE
            }
        })

        recyclerTags.adapter = filterAdapter
        recyclerTags.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)

        viewModel.filterItems.observe(this, Observer { filters ->
            filters?.let {
                filterAdapter.filters = it
                filterAdapter.notifyDataSetChanged()
            }
        })

        viewModel.transactionViewItemLiveEvent.observe(this, Observer { transactionViewItem ->
            transactionViewItem?.let {
                transInfoViewModel.setViewItem(it)
            }
        })

        viewModel.reloadLiveEvent.observe(this, Observer {
            transactionsAdapter.notifyDataSetChanged()
            if (transactionsAdapter.itemCount == 0) {
                viewModel.delegate.onBottomReached()
            }

            recyclerTransactions.visibility = if (viewModel.delegate.itemsCount == 0) View.GONE else View.VISIBLE
            emptyListText.visibility = if (viewModel.delegate.itemsCount == 0) View.VISIBLE else View.GONE
        })

        viewModel.addItemsLiveEvent.observe(this, Observer {
            it?.let { (fromIndex, count) ->
                transactionsAdapter.notifyItemRangeInserted(fromIndex, count)
            }
        })

        viewModel.reloadItemsLiveEvent.observe(this, Observer {
            it?.forEach { index ->
                transactionsAdapter.notifyItemChanged(index)
            }
        })

        setBottomSheet()
    }

    //Bottom sheet shows TransactionInfo
    private fun setBottomSheet() {

        bottomSheetBehavior = BottomSheetBehavior.from(nestedScrollView)

        transactionsDim.visibility = View.GONE
        transactionsDim.alpha = 0f

        var bottomSheetSlideOffOld = 0f

        bottomSheetBehavior?.setBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
            override fun onStateChanged(@NonNull bottomSheet: View, newState: Int) {}

            override fun onSlide(@NonNull bottomSheet: View, slideOffset: Float) {
                transactionsDim.alpha = slideOffset
                if (bottomSheetSlideOffOld >= 0.7 && slideOffset < 0.7) {
                    (activity as? MainActivity)?.setBottomNavigationVisible(true)
                } else if (bottomSheetSlideOffOld >= 0.8 && slideOffset > 0.9) {
                    (activity as? MainActivity)?.setBottomNavigationVisible(false)
                }

                transactionsDim.visibility = if (slideOffset == 0f) View.GONE else View.VISIBLE

                bottomSheetSlideOffOld = slideOffset
            }
        })

        transInfoViewModel = ViewModelProviders.of(this).get(TransactionInfoViewModel::class.java)
        transInfoViewModel.init()

        transactionIdView.setOnClickListener { transInfoViewModel.onClickTransactionId() }
        txtFullInfo.setOnClickListener { transInfoViewModel.onClickOpenFillInfo() }
        transactionsDim.setOnClickListener { bottomSheetBehavior?.state = BottomSheetBehavior.STATE_COLLAPSED }

        transInfoViewModel.showCopiedLiveEvent.observe(this, Observer {
            HudHelper.showSuccessMessage(R.string.Hud_Text_Copied)
        })

        transInfoViewModel.showFullInfoLiveEvent.observe(this, Observer { pair ->
            pair?.let {
                activity?.let { activity ->
                    FullTransactionInfoModule.start(activity, transactionHash = it.first, coinCode = it.second)
                }
            }
        })

        transInfoViewModel.transactionLiveData.observe(this, Observer { txRecord ->
            txRecord?.let { txRec ->
                (activity as? MainActivity)?.setBottomNavigationVisible(false)
                bottomSheetBehavior?.state = BottomSheetBehavior.STATE_EXPANDED

                val txStatus = txRec.status

                fiatValue.apply {
                    text = txRec.currencyValue?.let { ValueFormatter.format(it, showNegativeSign = true, realNumber = true) }
                    setTextColor(resources.getColor(if (txRec.incoming) R.color.green_crypto else R.color.yellow_crypto, null))
                }

                coinValue.apply {
                    text = ValueFormatter.format(txRec.coinValue, true, true)
                }

                itemTime.apply {
                    bindTime(title = getString(R.string.TransactionInfo_Time), time = txRec.date?.let { DateHelper.getFullDateWithShortMonth(it) } ?: "")
                }

                itemStatus.apply {
                    bindStatus(txStatus)
                }

                transactionIdView.bindTransactionId(txRec.transactionHash)

                itemFrom.apply {
                    setOnClickListener { transInfoViewModel.onClickFrom() }
                    visibility = if (txRec.from.isNullOrEmpty()) View.GONE else View.VISIBLE
                    bindAddress(title = getString(R.string.TransactionInfo_From), address = txRec.from, showBottomBorder = true)
                }

                itemTo.apply {
                    setOnClickListener { transInfoViewModel.onClickTo() }
                    visibility = if (txRec.to.isNullOrEmpty()) View.GONE else View.VISIBLE
                    bindAddress(title = getString(R.string.TransactionInfo_To), address = txRec.to, showBottomBorder = true)
                }
            }
        })
    }

    override fun onItemClick(item: TransactionViewItem) {
        viewModel.delegate.onTransactionItemClick(item)
    }

    override fun onFilterItemClick(item: String?) {
        viewModel.delegate.onFilterSelect(item)
    }

    fun onBackPressed(): Boolean {
        if (bottomSheetBehavior?.state == BottomSheetBehavior.STATE_EXPANDED) {
            bottomSheetBehavior?.state = BottomSheetBehavior.STATE_COLLAPSED
            return true
        }
        return false
    }

}

class TransactionsAdapter(private var listener: Listener) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), ViewHolderTransaction.ClickListener {

    init {
        setHasStableIds(true)
    }

    override fun getItemId(position: Int): Long {
        return viewModel.delegate.idForIndex(position)
    }

    interface Listener {
        fun onItemClick(item: TransactionViewItem)
    }

    lateinit var viewModel: TransactionsViewModel

    override fun getItemCount(): Int {
        return viewModel.delegate.itemsCount
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
            ViewHolderTransaction(LayoutInflater.from(parent.context).inflate(R.layout.view_holder_transaction, parent, false), this)

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if (position > itemCount - 3) {
            viewModel.delegate.onBottomReached()
        }

        when (holder) {
            is ViewHolderTransaction -> {
                holder.bind(viewModel.delegate.itemForIndex(position))
            }
        }
    }

    override fun onClick(position: Int) {
        listener.onItemClick(viewModel.delegate.itemForIndex(position))
    }
}

class ViewHolderTransaction(override val containerView: View, private val l: ClickListener ) : RecyclerView.ViewHolder(containerView), LayoutContainer {

    interface ClickListener {
        fun onClick(position: Int)
    }

    init {
        containerView.setOnSingleClickListener { l.onClick(adapterPosition) }
    }

    fun bind(transactionRecord: TransactionViewItem) {
        txValueInFiat.text = transactionRecord.currencyValue?.let { ValueFormatter.formatForTransactions(it, transactionRecord.incoming) }
        txValueInCoin.text = ValueFormatter.format(transactionRecord.coinValue, true)
        txDate.text = transactionRecord.date?.let { DateHelper.getShortDateForTransaction(it) }
        val time = transactionRecord.date?.let { DateHelper.getOnlyTime(it) }
        txStatusWithTimeView.bind(transactionRecord.status, time)
    }
}

class FilterAdapter(private var listener: Listener) : RecyclerView.Adapter<RecyclerView.ViewHolder>(), ViewHolderFilter.ClickListener {

    interface Listener {
        fun onFilterItemClick(item: String?)
    }

    private var selectedFilterId: String? = null
    var filters: List<String?> = listOf()
    var filterChangeable = true

    override fun getItemCount() = filters.size

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
            ViewHolderFilter(LayoutInflater.from(parent.context).inflate(R.layout.view_holder_filter, parent, false), this)

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder) {
            is ViewHolderFilter -> holder.bind(filters[position], selectedFilterId == filters[position])
        }
    }

    override fun onClickItem(position: Int) {
        if (filterChangeable) {
            listener.onFilterItemClick(filters[position])
            selectedFilterId = filters[position]
            notifyDataSetChanged()
        }
    }
}

class ViewHolderFilter(override val containerView: View, private val l: ClickListener) : RecyclerView.ViewHolder(containerView), LayoutContainer {

    interface ClickListener {
        fun onClickItem(position: Int)
    }

    fun bind(filterName: String?, active: Boolean) {
        filter_text.text = filterName ?: containerView.context.getString(R.string.Transactions_FilterAll)
        filter_text.isActivated = active
        filter_text.setOnClickListener { l.onClickItem(adapterPosition) }
    }
}

package io.horizontalsystems.bankwallet.modules.transactions

import io.horizontalsystems.bankwallet.core.ICurrencyManager
import io.horizontalsystems.bankwallet.core.IWalletManager
import io.horizontalsystems.bankwallet.core.managers.RateManager
import io.horizontalsystems.bankwallet.entities.TransactionRecord
import io.horizontalsystems.bankwallet.entities.Wallet
import io.reactivex.Single
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import java.util.concurrent.TimeUnit

class TransactionsInteractor(private val walletManager: IWalletManager, private val currencyManager: ICurrencyManager, private val rateManager: RateManager) : TransactionsModule.IInteractor {
    var delegate: TransactionsModule.IInteractorDelegate? = null

    private val disposables = CompositeDisposable()
    private val ratesDisposables = CompositeDisposable()
    private val lastBlockHeightDisposables = CompositeDisposable()
    private val transactionUpdatesDisposables = CompositeDisposable()
    private val requestedTimestamps = mutableMapOf<CoinCode, MutableList<Long>>()

    override fun initialFetch() {
        onUpdateCoinCodes()

        disposables.add(walletManager.walletsUpdatedSignal
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .subscribe {
                    onUpdateCoinCodes()
                })

        disposables.add(currencyManager.baseCurrencyUpdatedSignal
                .subscribeOn(Schedulers.io())
                .observeOn(Schedulers.io())
                .subscribe {
                    ratesDisposables.clear()
                    requestedTimestamps.clear()
                    delegate?.onUpdateBaseCurrency()
                })
    }

    override fun fetchRecords(fetchDataList: List<TransactionsModule.FetchData>) {
        if (fetchDataList.isEmpty()) {
            delegate?.didFetchRecords(mapOf())
            return
        }

        val flowables = mutableListOf<Single<Pair<CoinCode, List<TransactionRecord>>>>()

        fetchDataList.forEach { fetchData ->
            val adapter = walletManager.wallets.find { it.coinCode == fetchData.coinCode }?.adapter

            val flowable = when (adapter) {
                null -> Single.just(Pair(fetchData.coinCode, listOf()))
                else -> {
                    adapter.getTransactionsObservable(fetchData.hashFrom, fetchData.limit)
                            .map {
                                Pair(fetchData.coinCode, it)
                            }
                }
            }

            flowables.add(flowable)
        }

        disposables.add(
                Single.zip(flowables) {
                    val res = mutableMapOf<CoinCode, List<TransactionRecord>>()
                    it.forEach {
                        it as Pair<CoinCode, List<TransactionRecord>>
                        res[it.first] = it.second
                    }
                    res.toMap()
                }
                        .subscribeOn(Schedulers.io())
                        .observeOn(Schedulers.io())
                        .subscribe { records, t2 ->
                            delegate?.didFetchRecords(records)
                        })
    }

    override fun setSelectedCoinCodes(selectedCoinCodes: List<String>) {
        delegate?.onUpdateSelectedCoinCodes(if (selectedCoinCodes.isEmpty()) walletManager.wallets.map { it.coinCode } else selectedCoinCodes)
    }

    override fun fetchLastBlockHeights() {
        lastBlockHeightDisposables.clear()

        walletManager.wallets.forEach { wallet ->
            lastBlockHeightDisposables.add(wallet.adapter.lastBlockHeightUpdatedSignal
                    .throttleLast(3, TimeUnit.SECONDS)
                    .subscribeOn(Schedulers.io())
                    .observeOn(Schedulers.io())
                    .subscribe {
                        onUpdateLastBlockHeight(wallet)
                    })
        }
    }

    override fun fetchRates(timestamps: Map<CoinCode, List<Long>>) {
        val baseCurrency = currencyManager.baseCurrency
        val currencyCode = baseCurrency.code

        timestamps.forEach {
            val coinCode = it.key
            for (timestamp in it.value) {
                if (requestedTimestamps[coinCode]?.contains(timestamp) == true) continue

                if (!requestedTimestamps.containsKey(coinCode)) {
                    requestedTimestamps[coinCode] = mutableListOf()
                }
                requestedTimestamps[coinCode]?.add(timestamp)

                ratesDisposables.add(rateManager.rateValueObservable(coinCode, currencyCode, timestamp)
                        .subscribeOn(Schedulers.io())
                        .observeOn(Schedulers.io())
                        .subscribe {
                            delegate?.didFetchRate(it, coinCode, baseCurrency, timestamp)
                        })
            }
        }
    }

    override fun clear() {
        disposables.clear()
        lastBlockHeightDisposables.clear()
        ratesDisposables.clear()
    }

    private fun onUpdateLastBlockHeight(wallet: Wallet) {
        wallet.adapter.lastBlockHeight?.let { lastBlockHeight ->
            delegate?.onUpdateLastBlockHeight(wallet.coinCode, lastBlockHeight)
        }
    }

    private fun onUpdateCoinCodes() {
        delegate?.onUpdateCoinsData(walletManager.wallets.map { Triple(it.coinCode, it.adapter.confirmationsThreshold, it.adapter.lastBlockHeight) })

        walletManager.wallets.forEach { wallet ->
            transactionUpdatesDisposables.add(wallet.adapter.transactionRecordsSubject
                    .subscribeOn(Schedulers.io())
                    .observeOn(Schedulers.io())
                    .subscribe {
                        delegate?.didUpdateRecords(it, wallet.coinCode)
                    })
        }
    }

}

package io.horizontalsystems.bankwallet.modules.transactions

import io.horizontalsystems.bankwallet.entities.TransactionRecord

class TransactionsLoader(private val dataSource: TransactionRecordDataSource) {

    interface Delegate {
        fun didChangeData()
        fun didInsertData(fromIndex: Int, count: Int)
        fun fetchRecords(fetchDataList: List<TransactionsModule.FetchData>)
    }

    var delegate: Delegate? = null

    val itemsCount: Int
        get() = dataSource.itemsCount

    var loading: Boolean = false
    val allRecords: Map<CoinCode, List<TransactionRecord>>
        get() = dataSource.allRecords

    fun itemForIndex(index: Int) =
            dataSource.itemForIndex(index)

    fun setCoinCodes(coinCodes: List<CoinCode>) {
        dataSource.setCoinCodes(coinCodes)
    }

    fun loadNext(initial: Boolean = false) {
        if (loading) return
        loading = true

        if (dataSource.allShown) {
            if (initial) {
                delegate?.didChangeData()
            }
            loading = false
            return
        }

        val fetchDataList = dataSource.getFetchDataList()
        if (fetchDataList.isEmpty()) {
            val currentItemsCount = dataSource.itemsCount
            val insertedCount = dataSource.increasePage()
            if (insertedCount > 0) {
                delegate?.didInsertData(currentItemsCount, insertedCount)
            }
            loading = false
        } else {
            delegate?.fetchRecords(fetchDataList)
        }
    }

    fun didFetchRecords(records: Map<CoinCode, List<TransactionRecord>>) {
        dataSource.handleNextRecords(records)
        val currentItemsCount = dataSource.itemsCount
        val insertedCount = dataSource.increasePage()
        if (insertedCount > 0) {
            delegate?.didInsertData(currentItemsCount, insertedCount)
        }
        loading = false
    }

    fun itemIndexesForTimestamp(coinCode: CoinCode, timestamp: Long): List<Int> {
        return dataSource.itemIndexesForTimestamp(coinCode, timestamp)
    }

    fun didUpdateRecords(records: List<TransactionRecord>, coinCode: CoinCode) {
        if (dataSource.handleUpdatedRecords(records, coinCode)) {
            delegate?.didChangeData()
        }
    }

}

package io.horizontalsystems.bankwallet.modules.transactions

import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.core.factories.TransactionViewItemFactory
import io.horizontalsystems.bankwallet.entities.CoinValue
import io.horizontalsystems.bankwallet.entities.Currency
import io.horizontalsystems.bankwallet.entities.CurrencyValue
import io.horizontalsystems.bankwallet.entities.TransactionRecord
import java.math.BigDecimal
import java.util.*

typealias CoinCode = String

data class TransactionViewItem(
        val transactionHash: String,
        val coinValue: CoinValue,
        val currencyValue: CurrencyValue?,
        val from: String?,
        val to: String?,
        val incoming: Boolean,
        val date: Date?,
        val status: TransactionStatus)

sealed class TransactionStatus {
    object Pending : TransactionStatus()
    class Processing(val progress: Int) : TransactionStatus() //progress in 0..100%
    object Completed : TransactionStatus()
}

object TransactionsModule {

    data class FetchData(val coinCode: CoinCode, val hashFrom: String?, val limit: Int)

    interface IView {
        fun showFilters(filters: List<CoinCode?>)
        fun reload()
        fun reloadItems(updatedIndexes: List<Int>)
        fun addItems(fromIndex: Int, count: Int)
    }

    interface IViewDelegate {
        fun viewDidLoad()
        fun onTransactionItemClick(transaction: TransactionViewItem)
        fun onFilterSelect(coinCode: CoinCode?)
        fun onClear()

        val itemsCount: Int
        fun itemForIndex(index: Int): TransactionViewItem
        fun idForIndex(index: Int): Long
        fun onBottomReached()
    }

    interface IInteractor {
        fun initialFetch()
        fun clear()
        fun fetchRecords(fetchDataList: List<FetchData>)
        fun setSelectedCoinCodes(selectedCoinCodes: List<CoinCode>)
        fun fetchLastBlockHeights()
        fun fetchRates(timestamps: Map<CoinCode, List<Long>>)
    }

    interface IInteractorDelegate {
        fun onUpdateCoinsData(allCoinData: List<Triple<String, Int, Int?>>)
        fun onUpdateSelectedCoinCodes(selectedCoinCodes: List<CoinCode>)
        fun didFetchRecords(records: Map<CoinCode, List<TransactionRecord>>)
        fun onUpdateLastBlockHeight(coinCode: CoinCode, lastBlockHeight: Int)
        fun onUpdateBaseCurrency()
        fun didFetchRate(rateValue: BigDecimal, coinCode: CoinCode, currency: Currency, timestamp: Long)
        fun didUpdateRecords(records: List<TransactionRecord>, coinCode: CoinCode)
    }

    interface IRouter {
        fun openTransactionInfo(transactionViewItem: TransactionViewItem)
    }

    fun initModule(view: TransactionsViewModel, router: IRouter) {
        val dataSource = TransactionRecordDataSource(PoolRepo(), TransactionItemDataSource(), TransactionItemFactory())
        val interactor = TransactionsInteractor(App.walletManager, App.currencyManager, App.rateManager)
        val transactionsLoader = TransactionsLoader(dataSource)
        val presenter = TransactionsPresenter(interactor, router, TransactionViewItemFactory(App.walletManager, App.currencyManager, App.rateManager), transactionsLoader, TransactionMetadataDataSource())

        presenter.view = view
        interactor.delegate = presenter
        view.delegate = presenter
        transactionsLoader.delegate = presenter
    }

}

package io.horizontalsystems.bankwallet.modules.transactions

import io.horizontalsystems.bankwallet.core.factories.TransactionViewItemFactory
import io.horizontalsystems.bankwallet.entities.Currency
import io.horizontalsystems.bankwallet.entities.TransactionRecord
import java.math.BigDecimal

class TransactionsPresenter(private val interactor: TransactionsModule.IInteractor,
                            private val router: TransactionsModule.IRouter,
                            private val factory: TransactionViewItemFactory,
                            private val loader: TransactionsLoader,
                            private val metadataDataSource: TransactionMetadataDataSource) : TransactionsModule.IViewDelegate, TransactionsModule.IInteractorDelegate, TransactionsLoader.Delegate {

    var view: TransactionsModule.IView? = null

    override fun viewDidLoad() {
        interactor.initialFetch()
    }

    override fun onTransactionItemClick(transaction: TransactionViewItem) {
        router.openTransactionInfo(transaction)
    }

    override fun onFilterSelect(coinCode: CoinCode?) {
        interactor.setSelectedCoinCodes(coinCode?.let { listOf(coinCode) } ?: listOf())
    }

    override fun onClear() {
        interactor.clear()
    }

    override val itemsCount: Int
        get() = loader.itemsCount

    override fun itemForIndex(index: Int): TransactionViewItem {
        val transactionItem = loader.itemForIndex(index)
        val coinCode = transactionItem.coinCode
        val lastBlockHeight = metadataDataSource.getLastBlockHeight(coinCode)
        val threshold = metadataDataSource.getConfirmationThreshold(coinCode)
        val rate = metadataDataSource.getRate(coinCode, transactionItem.record.timestamp)

        return factory.item(transactionItem, lastBlockHeight, threshold, rate)
    }

    override fun idForIndex(index: Int): Long {
        return loader.itemForIndex(index).record.transactionHash.hashCode().toLong()
    }

    override fun onBottomReached() {
        loader.loadNext(false)
    }

    override fun onUpdateCoinsData(allCoinData: List<Triple<String, Int, Int?>>) {
        val coinCodes = allCoinData.map { it.first }

        loader.setCoinCodes(coinCodes)

        allCoinData.forEach { (coinCode, confirmationThreshold, lastBlockHeight) ->
            metadataDataSource.setConfirmationThreshold(confirmationThreshold, coinCode)
            lastBlockHeight?.let {
                metadataDataSource.setLastBlockHeight(it, coinCode)
            }
        }

        loader.loadNext(true)

        val filters = when {
            coinCodes.size < 2 -> listOf()
            else -> listOf(null).plus(coinCodes)
        }

        view?.showFilters(filters)

        interactor.fetchLastBlockHeights()
    }

    override fun onUpdateSelectedCoinCodes(selectedCoinCodes: List<CoinCode>) {
        loader.setCoinCodes(selectedCoinCodes)
        loader.loadNext(true)
    }

    override fun didFetchRecords(records: Map<CoinCode, List<TransactionRecord>>) {
        loader.didFetchRecords(records)
        fetchRatesForRecords(records)
    }

    override fun onUpdateLastBlockHeight(coinCode: CoinCode, lastBlockHeight: Int) {
        metadataDataSource.setLastBlockHeight(lastBlockHeight, coinCode)
        view?.reload()
    }

    override fun onUpdateBaseCurrency() {
        metadataDataSource.clearRates()
        view?.reload()

        fetchRatesForRecords(loader.allRecords)
    }

    override fun didFetchRate(rateValue: BigDecimal, coinCode: CoinCode, currency: Currency, timestamp: Long) {
        metadataDataSource.setRate(rateValue, coinCode, currency, timestamp)

        val itemIndexes = loader.itemIndexesForTimestamp(coinCode, timestamp)
        if (itemIndexes.isNotEmpty()) {
            view?.reloadItems(itemIndexes)
        }
    }

    override fun didUpdateRecords(records: List<TransactionRecord>, coinCode: CoinCode) {
        loader.didUpdateRecords(records, coinCode)
        fetchRatesForRecords(mapOf(coinCode to records))
    }

    override fun didChangeData() {
        view?.reload()
    }

    override fun didInsertData(fromIndex: Int, count: Int) {
        view?.addItems(fromIndex, count)
    }

    override fun fetchRecords(fetchDataList: List<TransactionsModule.FetchData>) {
        interactor.fetchRecords(fetchDataList)
    }

    private fun fetchRatesForRecords(records: Map<CoinCode, List<TransactionRecord>>) {
        interactor.fetchRates(records.map { Pair(it.key, it.value.map { it.timestamp }.distinct().sortedDescending()) }.toMap())
    }
}

package io.horizontalsystems.bankwallet.modules.transactions

import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel
import io.horizontalsystems.bankwallet.SingleLiveEvent

class TransactionsViewModel : ViewModel(), TransactionsModule.IView, TransactionsModule.IRouter {

    lateinit var delegate: TransactionsModule.IViewDelegate

    val filterItems = MutableLiveData<List<String?>>()
    val transactionViewItemLiveEvent = SingleLiveEvent<TransactionViewItem>()
    val reloadLiveEvent = SingleLiveEvent<Unit>()
    val reloadItemsLiveEvent = SingleLiveEvent<List<Int>>()
    val addItemsLiveEvent = SingleLiveEvent<Pair<Int, Int>>()

    fun init() {
        TransactionsModule.initModule(this, this)
        delegate.viewDidLoad()
    }

    override fun showFilters(filters: List<String?>) {
        filterItems.postValue(filters)
    }

    override fun reload() {
        reloadLiveEvent.postValue(Unit)
    }

    override fun reloadItems(updatedIndexes: List<Int>) {
        reloadItemsLiveEvent.postValue(updatedIndexes)
    }

    override fun addItems(fromIndex: Int, count: Int) {
        if (fromIndex == 0) {
            reloadLiveEvent.postValue(Unit)
        } else {
            addItemsLiveEvent.postValue(Pair(fromIndex, count))
        }
    }

    override fun openTransactionInfo(transactionViewItem: TransactionViewItem) {
        transactionViewItemLiveEvent.value = transactionViewItem
    }

    override fun onCleared() {
        delegate.onClear()
    }
}

package io.horizontalsystems.bankwallet.ui.dialogs

import android.app.Dialog
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.support.v4.app.DialogFragment
import android.support.v4.app.FragmentActivity
import android.support.v7.app.AlertDialog
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.*
import android.widget.Button
import io.horizontalsystems.bankwallet.R
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.view_holder_confirmation.*

class BottomConfirmAlert : DialogFragment(), ConfirmationsAdapter.Listener {

    interface Listener {
        fun onConfirmationSuccess()
    }

    private var mDialog: Dialog? = null
    private lateinit var rootView: View
    private lateinit var btnConfirm: Button
    private var checkboxItemList: MutableList<CheckBoxItem> = mutableListOf()
    private var adapter = ConfirmationsAdapter(this)
    private lateinit var recyclerView: RecyclerView

    private lateinit var listener: Listener
    private lateinit var color: BottomButtonColor

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val builder = activity?.let { AlertDialog.Builder(it, R.style.BottomDialog) }

        rootView = View.inflate(context, R.layout.fragment_bottom_confirmations, null) as ViewGroup
        btnConfirm = rootView.findViewById(R.id.btnConfirm)
        recyclerView = rootView.findViewById(R.id.recyclerView)
        builder?.setView(rootView)

        mDialog = builder?.create()
        mDialog?.window?.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT)
        mDialog?.window?.setGravity(Gravity.BOTTOM)
        mDialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))

        btnConfirm.setOnClickListener {
            listener.onConfirmationSuccess()
            dismiss()
        }

        btnConfirm.setBackgroundResource(getBackgroundResId(color))

        return mDialog as Dialog
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        recyclerView.adapter = adapter
        recyclerView.layoutManager = LinearLayoutManager(context)

        adapter.confirmations = checkboxItemList
        adapter.notifyDataSetChanged()
    }

    override fun onItemCheckMarkClick(position: Int, checked: Boolean) {
        checkboxItemList[position].checked = checked
        checkConfirmations()
    }

    private fun getBackgroundResId(color: BottomButtonColor): Int = when (color) {
        BottomButtonColor.RED -> R.drawable.button_red_background_12
        BottomButtonColor.YELLOW -> R.drawable.button_yellow_background_12
    }

    private fun checkConfirmations() {
        val uncheckedCount = checkboxItemList.asSequence().filter { !it.checked }.count()
        btnConfirm.isEnabled = uncheckedCount == 0
    }

    companion object {
        fun show(activity: FragmentActivity, textResourcesList: MutableList<Int>, listener: Listener, color: BottomButtonColor = BottomButtonColor.YELLOW) {
            val fragment = BottomConfirmAlert()
            fragment.listener = listener
            fragment.color = color
            textResourcesList.forEach {
                fragment.checkboxItemList.add(CheckBoxItem(it))
            }
            val ft = activity.supportFragmentManager.beginTransaction()
            ft.add(fragment, "bottom_confirm_alert")
            ft.commitAllowingStateLoss()
        }
    }
}

enum class BottomButtonColor {
    YELLOW, RED
}

class ConfirmationsAdapter(private var listener: Listener) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    interface Listener {
        fun onItemCheckMarkClick(position: Int, checked: Boolean)
    }

    var confirmations: List<CheckBoxItem> = listOf()

    override fun getItemCount() = confirmations.size

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
            ViewHolderConfirmation(LayoutInflater.from(parent.context).inflate(R.layout.view_holder_confirmation, parent, false))

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder) {
            is ViewHolderConfirmation -> holder.bind(confirmations[position]) { checked ->
                listener.onItemCheckMarkClick(position, checked)
                notifyDataSetChanged()
            }
        }
    }
}

class ViewHolderConfirmation(override val containerView: View) : RecyclerView.ViewHolder(containerView), LayoutContainer {

    fun bind(checkBoxItem: CheckBoxItem, onClick: ((Boolean) -> (Unit))) {
        confirmationCheckBox.text = containerView.context.getString(checkBoxItem.textRes)
        confirmationCheckBox.isChecked = checkBoxItem.checked

        confirmationCheckBox.setOnCheckedChangeListener { _, _ ->
            onClick.invoke(confirmationCheckBox.isChecked)
        }
    }
}

class CheckBoxItem(val textRes: Int, var checked: Boolean = false)

package io.horizontalsystems.bankwallet.ui.extensions

import android.content.Context
import android.support.constraint.ConstraintLayout
import android.util.AttributeSet
import android.view.View
import io.horizontalsystems.bankwallet.R
import kotlinx.android.synthetic.main.view_address.view.*

class AddressView : ConstraintLayout {

    constructor(context: Context) : super(context) {
        initializeViews()
    }

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
        initializeViews()
    }

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        initializeViews()
    }

    private fun initializeViews() {
        ConstraintLayout.inflate(context, R.layout.view_address, this)
    }

    fun bind(address: String) {
        txtAddress.text = address
        invalidate()
    }

    fun bindTransactionId(hash: String, withIcon: Boolean = true) {
        if (withIcon)
            iconImage.setImageResource(R.drawable.hash)
        else
            iconImage.visibility = View.GONE

        txtAddress.text = hash
        invalidate()
    }

}

package io.horizontalsystems.bankwallet.ui.extensions

import android.content.Context
import android.support.design.widget.AppBarLayout
import android.support.design.widget.CoordinatorLayout
import android.support.v4.view.ViewCompat
import android.util.AttributeSet
import android.view.View

// The item inside RecyclerView can't be clicked right after scrolling
// Google issue https://issuetracker.google.com/issues/66996774
// fix taken from here https://gist.github.com/chrisbanes/8391b5adb9ee42180893300850ed02f2

class FixAppBarLayoutBehavior(context: Context?, attrs: AttributeSet?) :
        AppBarLayout.Behavior(context, attrs) {

    override fun onNestedScroll(coordinatorLayout: CoordinatorLayout, child: AppBarLayout,
                                target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int,
                                dyUnconsumed: Int, type: Int) {
        super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed,
                dyUnconsumed, type)
        stopNestedScrollIfNeeded(dyUnconsumed, child, target, type)
    }

    override fun onNestedPreScroll(coordinatorLayout: CoordinatorLayout, child: AppBarLayout,
                                   target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) {
        super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type)
        stopNestedScrollIfNeeded(dy, child, target, type)
    }

    private fun stopNestedScrollIfNeeded(dy: Int, child: AppBarLayout, target: View, type: Int) {
        if (type == ViewCompat.TYPE_NON_TOUCH) {
            val currOffset = topAndBottomOffset
            if (dy < 0 && currOffset == 0 || dy > 0 && currOffset == -child.totalScrollRange) {
                ViewCompat.stopNestedScroll(target, ViewCompat.TYPE_NON_TOUCH)
            }
        }
    }
}

package io.horizontalsystems.bankwallet.ui.extensions

import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import io.horizontalsystems.bankwallet.R

class NumPadItemsAdapter(private val listener: Listener, bottomLeftButtonType: NumPadItemType, private val showLetters: Boolean = true) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    interface Listener {
        fun onItemClick(item: NumPadItem)
    }

    val numPadItems = listOf(
            NumPadItem(NumPadItemType.NUMBER, 1, ""),
            NumPadItem(NumPadItemType.NUMBER, 2, "abc"),
            NumPadItem(NumPadItemType.NUMBER, 3, "def"),
            NumPadItem(NumPadItemType.NUMBER, 4, "ghi"),
            NumPadItem(NumPadItemType.NUMBER, 5, "jkl"),
            NumPadItem(NumPadItemType.NUMBER, 6, "mno"),
            NumPadItem(NumPadItemType.NUMBER, 7, "pqrs"),
            NumPadItem(NumPadItemType.NUMBER, 8, "tuv"),
            NumPadItem(NumPadItemType.NUMBER, 9, "wxyz"),
            NumPadItem(bottomLeftButtonType, 0, "Bottom Left"),
            NumPadItem(NumPadItemType.NUMBER, 0, ""),
            NumPadItem(NumPadItemType.DELETE, 0, "Bottom Right")
    )

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return NumPadItemViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.view_numpad_button, parent, false))
    }

    override fun getItemCount() = numPadItems.count()

    var showFingerPrintButton = false
        set(value) {
            field = value
            notifyDataSetChanged()
        }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if (holder is NumPadItemViewHolder) {
            holder.bind(numPadItems[position], showFingerPrintButton, showLetters) { listener.onItemClick(numPadItems[position]) }
        }
    }

}

data class NumPadItem(val type: NumPadItemType, val number: Int, val letters: String)

enum class NumPadItemType {
    NUMBER, DELETE, FINGER, DOT
}

class NumPadItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

    private var txtNumber: TextView = itemView.findViewById(R.id.txtNumPadNumber)
    private var txtLetters: TextView = itemView.findViewById(R.id.txtNumPadText)
    private var imgBackSpace: ImageView = itemView.findViewById(R.id.imgBackSpace)
    private var imgFingerprint: ImageView = itemView.findViewById(R.id.imgFingerprint)

    fun bind(item: NumPadItem, isFingerprintEnabled: Boolean, showLetters: Boolean, onClick: () -> (Unit)) {

        itemView.setOnTouchListener { v, event ->
            when {
                event.action == MotionEvent.ACTION_DOWN -> {
                    onClick.invoke()
                    v.isPressed = true
                    true
                }
                event.action == MotionEvent.ACTION_UP -> {
                    v.isPressed = false
                    true
                }
                else -> false
            }
        }

        txtNumber.visibility = View.GONE
        txtLetters.visibility = View.GONE
        imgBackSpace.visibility = View.GONE
        imgFingerprint.visibility = View.GONE

        when (item.type) {
            NumPadItemType.DELETE -> {
                itemView.background = null
                imgBackSpace.visibility = View.VISIBLE
            }

            NumPadItemType.NUMBER -> {
                txtNumber.visibility = View.VISIBLE
                txtLetters.visibility = if (item.number == 0 || !showLetters) View.GONE else View.VISIBLE
                txtNumber.text = item.number.toString()
                txtLetters.text = item.letters
                itemView.setBackgroundResource(R.drawable.numpad_button_background)
            }

            NumPadItemType.FINGER -> {
                itemView.background = null
                imgFingerprint.visibility = if (isFingerprintEnabled) View.VISIBLE else View.GONE
            }

            NumPadItemType.DOT -> {
                itemView.background = null
                txtNumber.text = "."
                txtNumber.visibility = View.VISIBLE
            }

        }
    }
}

package io.horizontalsystems.bankwallet.ui.extensions

import android.content.Context
import android.graphics.PointF
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.LinearSmoothScroller
import android.support.v7.widget.RecyclerView
import android.util.DisplayMetrics

class SmoothLinearLayoutManager : LinearLayoutManager {

    private val millisecondsPreInch = 45f //default is 25f (bigger = slower)

    constructor(context: Context) : super(context, LinearLayoutManager.VERTICAL, false)

    constructor(context: Context, orientation: Int, reverseLayout: Boolean) : super(context, orientation, reverseLayout)

    override fun smoothScrollToPosition(recyclerView: RecyclerView, state: RecyclerView.State?,
                                        position: Int) {
        val smoothScroller = SmoothScroller(recyclerView.context)
        smoothScroller.targetPosition = position
        startSmoothScroll(smoothScroller)
    }

    private inner class SmoothScroller(context: Context) : LinearSmoothScroller(context) {

        override fun computeScrollVectorForPosition(targetPosition: Int): PointF? {
            return this@SmoothLinearLayoutManager.computeScrollVectorForPosition(targetPosition)
        }

        override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics?): Float {
            displayMetrics?.densityDpi?.let {
                return millisecondsPreInch / it
            }
            return super.calculateSpeedPerPixel(displayMetrics)
        }
    }
}

package io.horizontalsystems.bankwallet.ui.view

import android.view.View

abstract class SingleClickListener : View.OnClickListener {

    override fun onClick(v: View) {
        if (SingleClickManager.canBeClicked()) {
            onSingleClick(v)
        }
    }

    abstract fun onSingleClick(v: View)

}

package io.horizontalsystems.bankwallet.ui.view

import android.os.SystemClock

object SingleClickManager {

    private const val CLICK_TIME_DELTA: Long = 500 //milliseconds
    private var lastClickTime: Long = 0

    fun canBeClicked(): Boolean {
        val clickTime = SystemClock.elapsedRealtime()

        if (clickTime - lastClickTime > CLICK_TIME_DELTA) {
            lastClickTime  = clickTime
            return true
        }
        return false
    }

}

package io.horizontalsystems.bankwallet.ui.view

import android.support.v7.widget.RecyclerView
import android.view.View

import io.horizontalsystems.bankwallet.R

class ViewHolderProgressbar(itemView: View) : RecyclerView.ViewHolder(itemView) {

    fun bind(visible: Boolean) {
        itemView.findViewById<View>(R.id.progressBar).visibility = if (visible) View.VISIBLE else View.GONE
    }

    companion object {
        val layoutResourceId: Int
            get() = R.layout.view_holder_progressbar_item
    }
}

package io.horizontalsystems.bankwallet.modules.fulltransactioninfo.dataprovider

import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.content.Context
import android.os.Bundle
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import io.horizontalsystems.bankwallet.BaseActivity
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.core.setOnSingleClickListener
import io.horizontalsystems.bankwallet.ui.view.ViewHolderProgressbar
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.activity_explorer_switcher.*
import kotlinx.android.synthetic.main.view_holder_explorer_item.*

class DataProviderSettingsActivity : BaseActivity(), DataProviderSettingsAdapter.Listener {

    private var adapter: DataProviderSettingsAdapter? = null

    private lateinit var viewModel: DataProviderSettingsViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val coinCode = intent.extras.getString(DataProviderSettingsModule.COIN_CODE)
        val txHash = intent.extras.getString(DataProviderSettingsModule.TRANSACTION_HASH)
        viewModel = ViewModelProviders.of(this).get(DataProviderSettingsViewModel::class.java)
        viewModel.init(coinCode, txHash)

        setContentView(R.layout.activity_explorer_switcher)

        setSupportActionBar(toolbar)
        supportActionBar?.title = getString(R.string.FullInfo_Source)
        supportActionBar?.setDisplayHomeAsUpEnabled(true)
        supportActionBar?.setHomeAsUpIndicator(R.drawable.back)

        adapter = DataProviderSettingsAdapter(this)

        recyclerView.adapter = adapter
        recyclerView.layoutManager = LinearLayoutManager(this)

        viewModel.providerItems.observe(this, Observer { items ->
            items?.let {
                adapter?.items = it
                adapter?.notifyDataSetChanged()
            }
        })

        viewModel.closeLiveEvent.observe(this, Observer {
            finish()
        })
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            android.R.id.home -> {
                onBackPressed()
                return true
            }
        }

        return super.onOptionsItemSelected(item)
    }

    override fun onChangeProvider(item: DataProviderSettingsItem) {
        viewModel.delegate.onSelect(item)
    }
}

class DataProviderSettingsAdapter(private var listener: Listener) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    private val VIEW_TYPE_ITEM = 1
    private val VIEW_TYPE_LOADING = 2

    interface Listener {
        fun onChangeProvider(item: DataProviderSettingsItem)
    }

    var items = listOf<DataProviderSettingsItem>()

    override fun getItemCount() = if (items.isEmpty()) 1 else items.size

    override fun getItemViewType(position: Int): Int = if (items.isEmpty()) {
        VIEW_TYPE_LOADING
    } else {
        VIEW_TYPE_ITEM
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        return when (viewType) {
            VIEW_TYPE_ITEM -> {
                val containerView = inflater.inflate(ViewHolderDataProviderSettings.layoutResourceId, parent, false)
                ViewHolderDataProviderSettings(parent.context, containerView)
            }
            else -> ViewHolderProgressbar(inflater.inflate(ViewHolderProgressbar.layoutResourceId, parent, false))
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder) {
            is ViewHolderDataProviderSettings -> holder.bind(items[position]) { listener.onChangeProvider(items[position]) }
        }
    }

}

class ViewHolderDataProviderSettings(private val context: Context, override val containerView: View) : RecyclerView.ViewHolder(containerView), LayoutContainer {

    fun bind(item: DataProviderSettingsItem, onClick: () -> (Unit)) {

        containerView.setOnSingleClickListener { onClick.invoke() }
        title.text = item.name
        subtitle.text = context.getString(if (item.online) R.string.FullInfo_Source_Online else R.string.FullInfo_Source_Offline)

        if (item.online) {
            subtitle.setTextColor(subtitle.resources.getColor(R.color.green_crypto))
        } else {
            subtitle.setTextColor(subtitle.resources.getColor(R.color.red_warning))
        }

        if (item.checking) {
            statusChecking.visibility = View.VISIBLE
            subtitle.visibility = View.GONE
        } else {
            subtitle.visibility = View.VISIBLE
            statusChecking.visibility = View.GONE
        }

        checkmarkIcon.visibility = if (item.selected) View.VISIBLE else View.GONE
    }

    companion object {
        val layoutResourceId: Int
            get() = R.layout.view_holder_explorer_item
    }

}

package io.horizontalsystems.bankwallet.modules.fulltransactioninfo.dataprovider

import io.horizontalsystems.bankwallet.core.INetworkManager
import io.horizontalsystems.bankwallet.core.ITransactionDataProviderManager
import io.horizontalsystems.bankwallet.modules.fulltransactioninfo.FullTransactionInfoModule
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import java.net.URL

class DataProviderSettingsInteractor(private val dataProviderManager: ITransactionDataProviderManager, val networkManager: INetworkManager)
    : DataProviderSettingsModule.Interactor {

    val disposables = CompositeDisposable()
    var delegate: DataProviderSettingsModule.InteractorDelegate? = null

    override fun pingProvider(name: String, url: String) {
        val uri = URL(url)
        val host = "${uri.protocol}://${uri.host}"

        disposables.add(networkManager.ping(host, url)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe({
                    delegate?.onPingSuccess(name)
                }, {
                    delegate?.onPingFailure(name)
                }))
    }

    override fun providers(coinCode: CoinCode): List<FullTransactionInfoModule.Provider> {
        return dataProviderManager.providers(coinCode)
    }

    override fun baseProvider(coinCode: CoinCode): FullTransactionInfoModule.Provider {
        return dataProviderManager.baseProvider(coinCode)
    }

    override fun setBaseProvider(name: String, coinCode: CoinCode) {
        dataProviderManager.setBaseProvider(name, coinCode)

        delegate?.onSetDataProvider()
    }
}

package io.horizontalsystems.bankwallet.modules.fulltransactioninfo.dataprovider

import android.content.Context
import android.content.Intent
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.modules.fulltransactioninfo.FullTransactionInfoModule
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode

object DataProviderSettingsModule {
    const val COIN_CODE = "coin_code"
    const val TRANSACTION_HASH = "transaction_hash"

    interface View {
        fun show(items: List<DataProviderSettingsItem>)
        fun close()
    }

    interface ViewDelegate {
        fun viewDidLoad()
        fun onSelect(item: DataProviderSettingsItem)
    }

    interface Interactor {
        fun pingProvider(name: String, url: String)
        fun providers(coinCode: CoinCode): List<FullTransactionInfoModule.Provider>
        fun baseProvider(coinCode: CoinCode): FullTransactionInfoModule.Provider
        fun setBaseProvider(name: String, coinCode: CoinCode)
    }

    interface InteractorDelegate {
        fun onPingSuccess(name: String)
        fun onPingFailure(name: String)
        fun onSetDataProvider()
    }

    fun init(coinCode: CoinCode, transactionHash: String, view: DataProviderSettingsViewModel) {
        val interactor = DataProviderSettingsInteractor(App.transactionDataProviderManager, App.networkManager)
        val presenter = DataProviderSettingsPresenter(coinCode, transactionHash, interactor)

        view.delegate = presenter
        presenter.view = view
        interactor.delegate = presenter
    }

    fun start(context: Context, coinCode: CoinCode, transactionHash: String) {
        val intent = Intent(context, DataProviderSettingsActivity::class.java)
        intent.putExtra(COIN_CODE, coinCode)
        intent.putExtra(TRANSACTION_HASH, transactionHash)
        context.startActivity(intent)
    }

}

data class DataProviderSettingsItem(val name: String, var online: Boolean, val selected: Boolean, var checking: Boolean) {

    override fun equals(other: Any?): Boolean {
        if (other is DataProviderSettingsItem) {
            return name == other.name
        }

        return super.equals(other)
    }

    override fun hashCode(): Int {
        var result = name.hashCode()
        result = 31 * result + online.hashCode()
        result = 31 * result + selected.hashCode()
        result = 31 * result + checking.hashCode()
        return result
    }
}

package io.horizontalsystems.bankwallet.modules.fulltransactioninfo.dataprovider

import io.horizontalsystems.bankwallet.modules.transactions.CoinCode

class DataProviderSettingsPresenter(val coinCode: CoinCode, val transactionHash: String, private val interactor: DataProviderSettingsModule.Interactor)
    : DataProviderSettingsModule.ViewDelegate, DataProviderSettingsModule.InteractorDelegate {

    var view: DataProviderSettingsModule.View? = null
    var items: List<DataProviderSettingsItem> = listOf()

    override fun viewDidLoad() {
        val baseProvider = interactor.baseProvider(coinCode)
        val allProviders = interactor.providers(coinCode)

        allProviders.forEach { provider ->
            interactor.pingProvider(provider.name, provider.apiUrl(transactionHash))
        }

        items = allProviders.map {
            DataProviderSettingsItem(name = it.name, online = false, selected = it.name == baseProvider.name, checking = true)
        }

        view?.show(items)
    }

    override fun onPingSuccess(name: String) {
        items.filter { it.name == name }.map {
            it.online = true
            it.checking = false
        }

        view?.show(items)
    }

    override fun onPingFailure(name: String) {
        items.filter { it.name == name }.map {
            it.online = false
            it.checking = false
        }

        view?.show(items)
    }

    override fun onSelect(item: DataProviderSettingsItem) {
        if (!item.selected) {
            interactor.setBaseProvider(item.name, coinCode)
        }
    }

    override fun onSetDataProvider() {
        view?.close()
    }

}

package io.horizontalsystems.bankwallet.modules.fulltransactioninfo.dataprovider

import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel
import io.horizontalsystems.bankwallet.SingleLiveEvent
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode

class DataProviderSettingsViewModel : ViewModel(), DataProviderSettingsModule.View {

    lateinit var delegate: DataProviderSettingsModule.ViewDelegate

    val providerItems = MutableLiveData<List<DataProviderSettingsItem>>()
    val closeLiveEvent = SingleLiveEvent<Unit>()

    fun init(coinCode: CoinCode, transactionHash: String) {
        DataProviderSettingsModule.init(coinCode, transactionHash, this)
        delegate.viewDidLoad()
    }

    override fun show(items: List<DataProviderSettingsItem>) {
        providerItems.value = items
    }

    override fun close() {
        closeLiveEvent.call()
    }
}

package io.horizontalsystems.bankwallet.modules.fulltransactioninfo.providers

import com.google.gson.Gson
import com.google.gson.JsonObject
import com.google.gson.annotations.SerializedName
import io.horizontalsystems.bankwallet.modules.fulltransactioninfo.BitcoinResponse
import io.horizontalsystems.bankwallet.modules.fulltransactioninfo.EthereumResponse
import io.horizontalsystems.bankwallet.modules.fulltransactioninfo.FullTransactionInfoModule
import io.horizontalsystems.bankwallet.modules.fulltransactioninfo.FullTransactionResponse
import io.horizontalsystems.bankwallet.viewHelpers.ValueFormatter
import java.text.SimpleDateFormat
import java.util.*

class BlockChairBitcoinProvider : FullTransactionInfoModule.BitcoinForksProvider {
    override val name = "BlockChair.com"

    override fun url(hash: String): String {
        return "https://blockchair.com/bitcoin/transaction/$hash"
    }

    override fun apiUrl(hash: String): String {
        return "https://api.blockchair.com/bitcoin/dashboards/transaction/$hash"
    }

    override fun convert(json: JsonObject): BitcoinResponse {
        val response = Gson().fromJson(json, BlockchairBTCResponse::class.java)
        val transaction = response.data.entries.firstOrNull()
                ?: throw Exception("Failed to parse transaction response")

        return transaction.value
    }
}

class BlockChairBitcoinCashProvider : FullTransactionInfoModule.BitcoinForksProvider {
    override val name = "BlockChair.com"

    override fun url(hash: String): String {
        return "https://blockchair.com/bitcoin-cash/transaction/$hash"
    }

    override fun apiUrl(hash: String): String {
        return "https://api.blockchair.com/bitcoin-cash/dashboards/transaction/$hash"
    }

    override fun convert(json: JsonObject): BitcoinResponse {
        val response = Gson().fromJson(json, BlockchairBTCResponse::class.java)
        val transaction = response.data.entries.firstOrNull()
                ?: throw Exception("Failed to parse transaction response")

        return transaction.value
    }
}

class BlockChairEthereumProvider : FullTransactionInfoModule.EthereumForksProvider {
    override val name = "BlockChair.com"

    override fun url(hash: String): String {
        return "https://blockchair.com/ethereum/transaction/$hash"
    }

    override fun apiUrl(hash: String): String {
        return "https://api.blockchair.com/ethereum/dashboards/transaction/$hash"
    }

    override fun convert(json: JsonObject): EthereumResponse {
        val response = Gson().fromJson(json, BlockchairETHResponse::class.java)
        val transaction = response.data.entries.firstOrNull()
                ?: throw Exception("Failed to parse transaction response")

        return transaction.value
    }
}

class BlockchairBTCResponse(@SerializedName("data") val data: Map<String, Data>) {

    class Data(@SerializedName("transaction") val transaction: Transaction, @SerializedName("inputs") val vin: ArrayList<Vin>, @SerializedName("outputs") val vout: ArrayList<Vout>) : BitcoinResponse() {
        override val hash get() = transaction.hash
        override val height get() = transaction.height
        override val fee get() = transaction.fee.toDouble() / btcRate
        override val size get() = transaction.size
        override val feePerByte: Double? get() = null
        override val confirmations: String? = null
        override val inputs get() = vin as ArrayList<Input>
        override val outputs get() = vout as ArrayList<Output>
        override val date: Date
            get() {
                val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
                return dateFormat.parse(transaction.time)
            }
    }

    class Transaction(
            @SerializedName("hash") val hash: String,
            @SerializedName("time") val time: String,
            @SerializedName("size") val size: Int,
            @SerializedName("block_id") val height: Int,
            @SerializedName("fee") val fee: Int
    )

    class Vin(@SerializedName("value") val amount: Double, @SerializedName("recipient") override val address: String) : BitcoinResponse.Input() {
        override val value: Double
            get() = amount / BitcoinResponse.btcRate
    }

    class Vout(@SerializedName("value") val amount: Double, @SerializedName("recipient") override val address: String) : BitcoinResponse.Output() {
        override val value: Double
            get() = amount / BitcoinResponse.btcRate
    }
}

class BlockchairETHResponse(@SerializedName("data") val data: Map<String, Data>) : FullTransactionResponse {

    class Data(@SerializedName("transaction") val transaction: Transaction) : EthereumResponse() {
        override val size: Int? get() = null
        override val hash get() = transaction.hash
        override val height get() = transaction.height.toString()
        override val fee get() = (transaction.fee.toDouble() / ethRate).toString()
        override val from get() = transaction.sender
        override val to get() = transaction.recipient
        override val value get() = ValueFormatter.format(transaction.value.toBigInteger().toDouble() / ethRate)
        override val nonce get() = transaction.nonce.toInt().toString()
        override val gasLimit get() = transaction.gasLimit.toString()
        override val gasPrice get() = (transaction.gasPrice / gweiRate).toString()
        override val gasUsed get () = transaction.gasUsed.toString()
        override val confirmations: Int? get() = null
        override val date: Date
            get() {
                val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
                return dateFormat.parse(transaction.time)
            }
    }

    class Transaction(
            @SerializedName("hash") val hash: String,
            @SerializedName("time") val time: String,
            @SerializedName("size") val size: Int,
            @SerializedName("block_id") val height: Int,
            @SerializedName("fee") val fee: String,
            @SerializedName("gas_limit") val gasLimit: Long,
            @SerializedName("gas_price") val gasPrice: Long,
            @SerializedName("gas_used") val gasUsed: Long,
            @SerializedName("nonce") val nonce: String,
            @SerializedName("value") val value: String,
            @SerializedName("sender") val sender: String,
            @SerializedName("recipient") val recipient: String
    )
}

package io.horizontalsystems.bankwallet.modules.fulltransactioninfo.providers

import com.google.gson.Gson
import com.google.gson.JsonObject
import com.google.gson.annotations.SerializedName
import io.horizontalsystems.bankwallet.modules.fulltransactioninfo.BitcoinResponse
import io.horizontalsystems.bankwallet.modules.fulltransactioninfo.FullTransactionInfoModule
import java.util.*

class BlockExplorerBitcoinProvider : FullTransactionInfoModule.BitcoinForksProvider {
    override val name = "BlockExplorer.com"

    override fun url(hash: String): String {
        return "https://blockexplorer.com/tx/$hash"
    }

    override fun apiUrl(hash: String): String {
        return "https://blockexplorer.com/api/tx/$hash"
    }

    override fun convert(json: JsonObject): BitcoinResponse {
        return Gson().fromJson(json, BlockExplorerResponse::class.java)
    }
}

class BlockExplorerBitcoinCashProvider : FullTransactionInfoModule.BitcoinForksProvider {
    override val name = "BlockExplorer.com"

    override fun url(hash: String): String {
        return "https://bitcoincash.blockexplorer.com/tx/$hash"
    }

    override fun apiUrl(hash: String): String {
        return "https://bitcoincash.blockexplorer.com/api/tx/$hash"
    }

    override fun convert(json: JsonObject): BitcoinResponse {
        return Gson().fromJson(json, BlockExplorerResponse::class.java)
    }
}

class BlockExplorerResponse(
        @SerializedName("txid") override val hash: String,
        @SerializedName("blockheight") override val height: Int,
        @SerializedName("fees") val fees: Double,
        @SerializedName("time") val time: Long,
        @SerializedName("size") override val size: Int,
        @SerializedName("confirmations") val confirms: Int,
        @SerializedName("vin") val vin: ArrayList<Vin>,
        @SerializedName("vout") val vout: ArrayList<Vout>) : BitcoinResponse() {

    override val date get() = Date(time * 1000)
    override val fee get() = fees
    override val feePerByte get() = fees * btcRate / size
    override val confirmations get() = confirms.toString()
    override val inputs get() = vin as ArrayList<Input>
    override val outputs get() = vout as ArrayList<Output>

    class Vin(@SerializedName("valueSat") val amount: Int, @SerializedName("addr") override val address: String) : Input() {
        override val value: Double get() = amount / btcRate
    }

    class Vout(@SerializedName("value") val amount: String, @SerializedName("scriptPubKey") val pubKey: PubKey) : Output() {
        override val value: Double get() = amount.toDouble()
        override val address: String get() = pubKey.addresses.firstOrNull() ?: "..."
    }

    class PubKey(@SerializedName("addresses") val addresses: ArrayList<String>)
}

package io.horizontalsystems.bankwallet.modules.fulltransactioninfo.providers

import com.google.gson.Gson
import com.google.gson.JsonObject
import com.google.gson.annotations.SerializedName
import io.horizontalsystems.bankwallet.modules.fulltransactioninfo.BitcoinResponse
import io.horizontalsystems.bankwallet.modules.fulltransactioninfo.FullTransactionInfoModule
import java.util.*

class BtcComBitcoinProvider : FullTransactionInfoModule.BitcoinForksProvider {
    override val name = "Btc.com"

    override fun url(hash: String): String {
        return "https://btc.com/$hash"
    }

    override fun apiUrl(hash: String): String {
        return "https://chain.api.btc.com/v3/tx/$hash"
    }

    override fun convert(json: JsonObject): BitcoinResponse {
        return Gson().fromJson(json["data"], BtcComResponse::class.java)
    }
}

class BtcComBitcoinCashProvider : FullTransactionInfoModule.BitcoinForksProvider {
    override val name = "Btc.com"

    override fun url(hash: String): String {
        return "https://bch.btc.com/$hash"
    }

    override fun apiUrl(hash: String): String {
        return "https://bch-chain.api.btc.com/v3/tx/$hash"
    }

    override fun convert(json: JsonObject): BitcoinResponse {
        return Gson().fromJson(json["data"], BtcComResponse::class.java)
    }
}

class BtcComResponse(
        @SerializedName("fee") val fees: Int,
        @SerializedName("inputs") val vin: ArrayList<Vin>,
        @SerializedName("outputs") val vout: ArrayList<Vout>,
        @SerializedName("confirmations") override val confirmations: String,
        @SerializedName("hash") override val hash: String,
        @SerializedName("block_height") override val height: Int,
        @SerializedName("block_time") val time: Long,
        @SerializedName("size") override val size: Int) : BitcoinResponse() {

    override val fee get() = fees.toDouble() / btcRate
    override val feePerByte: Double? get() = null
    override val date: Date get() = Date(time * 1000)
    override val inputs get() = vin as ArrayList<Input>
    override val outputs get() = vout as ArrayList<Output>

    class Vin(@SerializedName("prev_value") val amount: Long, @SerializedName("prev_addresses") val addresses: ArrayList<String>) : Input() {
        override val value: Double get() = amount / btcRate
        override val address: String get() = addresses.firstOrNull() ?: ""
    }

    class Vout(@SerializedName("value") val amount: Double, @SerializedName("addresses") val addresses: ArrayList<String>) : Output() {
        override val value: Double get() = amount / btcRate
        override val address: String get() = addresses.firstOrNull() ?: ""
    }
}

package io.horizontalsystems.bankwallet.modules.fulltransactioninfo.providers

import com.google.gson.Gson
import com.google.gson.JsonObject
import com.google.gson.annotations.SerializedName
import io.horizontalsystems.bankwallet.modules.fulltransactioninfo.EthereumResponse
import io.horizontalsystems.bankwallet.modules.fulltransactioninfo.FullTransactionInfoModule
import io.horizontalsystems.bankwallet.viewHelpers.ValueFormatter
import java.math.BigInteger
import java.util.*

class EtherscanEthereumProvider : FullTransactionInfoModule.EthereumForksProvider {
    override val name: String = "Etherscan.io"

    override fun url(hash: String): String {
        return "https://etherscan.io/tx/$hash"
    }

    override fun apiUrl(hash: String): String {
        return "https://api.etherscan.io/api?module=proxy&action=eth_getTransactionByHash&txhash=$hash"
    }

    override fun convert(json: JsonObject): EthereumResponse {
        return Gson().fromJson(json["result"], EtherscanResponse::class.java)
    }
}

class EtherscanResponse(
        @SerializedName("hash") override val hash: String,
        @SerializedName("from") override val from: String,
        @SerializedName("to") override val to: String,
        @SerializedName("nonce") val gNonce: String,
        @SerializedName("value") val amount: String,
        @SerializedName("gas") val gLimit: String,
        @SerializedName("gasPrice") val price: String,
        @SerializedName("blockNumber") val blockNumber: String) : EthereumResponse() {

    override val date: Date? get() = null
    override val confirmations: Int? get() = null
    override val gasUsed: String? get() = null
    override val fee: String? = null
    override val size: Int? get() = null

    override val height: String
        get() = Integer.parseInt(blockNumber.substring(2), 16).toString()

    override val value: String
        get() = ValueFormatter.format(BigInteger(amount.substring(2), 16).toDouble() / ethRate)

    override val nonce: String
        get() = Integer.parseInt(gNonce.substring(2), 16).toString()

    override val gasLimit: String
        get() = BigInteger(gLimit.substring(2), 16).toString()

    override val gasPrice: String
        get() = (BigInteger(price.substring(2), 16).toDouble() / gweiRate).toInt().toString()
}

package io.horizontalsystems.bankwallet.modules.fulltransactioninfo.providers

import com.google.gson.Gson
import com.google.gson.JsonObject
import com.google.gson.annotations.SerializedName
import io.horizontalsystems.bankwallet.modules.fulltransactioninfo.BitcoinResponse
import io.horizontalsystems.bankwallet.modules.fulltransactioninfo.EthereumResponse
import io.horizontalsystems.bankwallet.modules.fulltransactioninfo.FullTransactionInfoModule
import java.math.BigInteger
import java.util.*

class HorsysBitcoinProvider(val testMode: Boolean) : FullTransactionInfoModule.BitcoinForksProvider {
    override val name = "HorizontalSystems.xyz"

    override fun url(hash: String): String {
        return "${if (testMode) "http://btc-testnet" else "https://btc"}.horizontalsystems.xyz/apg/tx/$hash"
    }

    override fun apiUrl(hash: String): String {
        return "${if (testMode) "http://btc-testnet" else "https://btc"}.horizontalsystems.xyz/apg/tx/$hash"
    }

    override fun convert(json: JsonObject): BitcoinResponse {
        return Gson().fromJson(json, HorsysBTCResponse::class.java)
    }
}

class HorsysBitcoinCashProvider(val testMode: Boolean) : FullTransactionInfoModule.BitcoinForksProvider {
    override val name: String = "HorizontalSystems.xyz"

    override fun url(hash: String): String {
        return "${if (testMode) "http://bch-testnet" else "https://bch"}.horizontalsystems.xyz/apg/tx/$hash"
    }

    override fun apiUrl(hash: String): String {
        return "${if (testMode) "http://bch-testnet" else "https://bch"}.horizontalsystems.xyz/apg/tx/$hash"
    }

    override fun convert(json: JsonObject): BitcoinResponse {
        return Gson().fromJson(json, HorsysBTCResponse::class.java)
    }
}

class HorsysEthereumProvider(val testMode: Boolean) : FullTransactionInfoModule.EthereumForksProvider {

    override val name: String = "HorizontalSystems.xyz"

    override fun url(hash: String): String {
        return "${if (testMode) "http://eth-testnet" else "https://eth"}.horizontalsystems.xyz/apg/tx/$hash"
    }

    override fun apiUrl(hash: String): String {
        return "${if (testMode) "http://eth-testnet" else "https://eth"}.horizontalsystems.xyz/apg/tx/$hash"
    }

    override fun convert(json: JsonObject): EthereumResponse {
        return Gson().fromJson(json, HorsysETHResponse::class.java)
    }
}

class HorsysBTCResponse(
        @SerializedName("fee") val fees: Int,
        @SerializedName("time") val time: Long,
        @SerializedName("rate") val rate: Int,
        @SerializedName("inputs") val vin: ArrayList<Vin>,
        @SerializedName("outputs") val vout: ArrayList<Vout>,
        @SerializedName("hash") override val hash: String,
        @SerializedName("height") override val height: Int,
        @SerializedName("confirmations") override val confirmations: String) : BitcoinResponse() {

    override val date get() = Date(time * 1000)
    override val inputs get() = vin as ArrayList<Input>
    override val outputs get() = vout as ArrayList<Output>
    override val feePerByte get() = rate.toDouble() / 1000
    override val fee: Double get() = fees / btcRate
    override val size: Int? get() = ((fee / feePerByte) * btcRate).toInt()

    class Vin(@SerializedName("coin") val coin: Coin) : Input() {
        override val value get() = coin.amount.toDouble() / btcRate
        override val address get() = coin.addr
    }

    class Vout(@SerializedName("value") val amount: Int, @SerializedName("address") val addr: String) : Output() {
        override val value get() = amount.toDouble() / btcRate
        override val address get() = addr
    }

    class Coin(@SerializedName("value") val amount: Int, @SerializedName("address") val addr: String)
}

class HorsysETHResponse(
        @SerializedName("nonce") val gNonce: String,
        @SerializedName("value") val amount: String,
        @SerializedName("gasPrice") val price: String,
        @SerializedName("blockNumber") val blockNumber: String,
        @SerializedName("hash") override val hash: String,
        @SerializedName("from") override val from: String,
        @SerializedName("to") override val to: String,
        @SerializedName("fee") override val fee: String,
        @SerializedName("gas") override val gasLimit: String,
        @SerializedName("gasUsed") override val gasUsed: String) : EthereumResponse() {

    override val size: Int? get() = null
    override val date: Date? get() = null
    override val confirmations: Int? get() = null
    override val height: String
        get() = Integer.parseInt(blockNumber, 16).toString()

    override val value: String
        get() = (amount.toDouble() / ethRate).toString()

    override val nonce: String
        get() = Integer.parseInt(gNonce, 16).toString()

    override val gasPrice: String
        get() = (BigInteger(price, 16).toDouble() / gweiRate).toInt().toString()
}

package io.horizontalsystems.bankwallet.modules.pin.edit

import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.core.IKeyStoreSafeExecute
import io.horizontalsystems.bankwallet.modules.pin.PinInteractor
import io.horizontalsystems.bankwallet.modules.pin.PinViewModel

object EditPinModule {

    interface IEditPinRouter {
        fun dismiss()
    }

    fun init(view: PinViewModel, router: IEditPinRouter, keystoreSafeExecute: IKeyStoreSafeExecute) {

        val interactor = PinInteractor(App.pinManager, App.authManager, keystoreSafeExecute)
        val presenter = EditPinPresenter(interactor, router)

        view.delegate = presenter
        presenter.view = view
        interactor.delegate = presenter
    }

}

package io.horizontalsystems.bankwallet.modules.pin.edit

import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.modules.pin.ManagePinPresenter
import io.horizontalsystems.bankwallet.modules.pin.PinModule
import io.horizontalsystems.bankwallet.modules.pin.PinPage

class EditPinPresenter(
        interactor: PinModule.IPinInteractor,
        private val router: EditPinModule.IEditPinRouter): ManagePinPresenter(interactor, pages = listOf(Page.UNLOCK, Page.ENTER, Page.CONFIRM)) {

    override fun viewDidLoad() {
        view?.setTitle(R.string.EditPin_Title)
        val pinPages = mutableListOf<PinPage>()

        pages.forEach { page ->
            when(page) {
                Page.UNLOCK -> pinPages.add(PinPage(R.string.EditPin_UnlockInfo))
                Page.ENTER -> pinPages.add(PinPage(R.string.EditPin_NewPinInfo))
                Page.CONFIRM -> pinPages.add(PinPage(R.string.SetPin_ConfirmInfo))
            }
        }
        view?.addPages(pinPages)
        view?.showCancel()
    }

    override fun onCancel() {
        router.dismiss()
    }

    override fun didSavePin() {
        router.dismiss()
    }

    override fun onBackPressed() {
        router.dismiss()
    }
}

package io.horizontalsystems.bankwallet.modules.pin.set

import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.core.IKeyStoreSafeExecute
import io.horizontalsystems.bankwallet.modules.pin.PinInteractor
import io.horizontalsystems.bankwallet.modules.pin.PinViewModel

object SetPinModule {

    interface ISetPinRouter {
        fun navigateToMain()
    }

    fun init(view: PinViewModel, router: ISetPinRouter, keystoreSafeExecute: IKeyStoreSafeExecute) {

        val interactor = PinInteractor(App.pinManager, App.authManager, keystoreSafeExecute)
        val presenter = SetPinPresenter(interactor, router)

        view.delegate = presenter
        presenter.view = view
        interactor.delegate = presenter
    }

}

package io.horizontalsystems.bankwallet.modules.pin.set

import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.modules.pin.ManagePinPresenter
import io.horizontalsystems.bankwallet.modules.pin.PinModule
import io.horizontalsystems.bankwallet.modules.pin.PinPage

class SetPinPresenter(
        private val interactor: PinModule.IPinInteractor,
        private val router: SetPinModule.ISetPinRouter): ManagePinPresenter(interactor, pages = listOf(Page.ENTER, Page.CONFIRM)) {

    override fun viewDidLoad() {
        view?.setTitle(R.string.SetPin_Title)

        val pinPages = mutableListOf<PinPage>()
        pages.forEach { page ->
            when(page) {
                Page.ENTER -> pinPages.add(PinPage(R.string.SetPin_Info))
                Page.CONFIRM -> pinPages.add(PinPage(R.string.SetPin_ConfirmInfo))
            }
        }
        view?.addPages(pinPages)
    }

    override fun didSavePin() {
        interactor.startAdapters()
    }

    override fun didStartedAdapters() {
        router.navigateToMain()
    }
}

package io.horizontalsystems.bankwallet.modules.pin.unlock

import io.horizontalsystems.bankwallet.core.*
import io.horizontalsystems.bankwallet.core.managers.AuthManager
import io.horizontalsystems.bankwallet.core.managers.OneTimeTimer
import io.horizontalsystems.bankwallet.entities.LockoutState

class UnlockPinInteractor(
        private val keystoreSafeExecute: IKeyStoreSafeExecute,
        private val localStorage: ILocalStorage,
        private val authManager: AuthManager,
        private val pinManager: IPinManager,
        private val lockManager: ILockManager,
        private val encryptionManager: IEncryptionManager,
        private val lockoutManager: ILockoutManager,
        private val timer: OneTimeTimer) : UnlockPinModule.IUnlockPinInteractor, IOneTimerDelegate {

    var delegate: UnlockPinModule.IUnlockPinInteractorDelegate? = null

    init {
        timer.delegate = this
    }

    //we cache secured data here, since its main Entry point for logged in user
    override fun cacheSecuredData() {
        keystoreSafeExecute.safeExecute(
                action = Runnable {
                    if (pinManager.pin.isNullOrEmpty() && pinManager.isPinSet) {
                        pinManager.safeLoad()
                    }
                    if (authManager.authData == null) {
                        authManager.safeLoad()
                    }
                    if (isBiometricOn()) {
                        encryptionManager.getCryptoObject()?.let { delegate?.setCryptoObject(it) }
                    }
                }
        )
    }

    override fun isBiometricOn(): Boolean {
        return localStorage.isBiometricOn
    }

    override fun unlock(pin: String): Boolean {
        val valid = pinManager.validate(pin)
        if (valid) {
            lockManager.onUnlock()
            lockoutManager.dropFailedAttempts()
        } else {
            lockoutManager.didFailUnlock()
            updateLockoutState()
        }

        return valid
    }

    override fun onUnlock() {
        delegate?.unlock()
        lockManager.onUnlock()
    }

    override fun onFire() {
        updateLockoutState()
    }

    override fun updateLockoutState() {
        val state = lockoutManager.currentState
        when(state) {
            is LockoutState.Locked -> timer.schedule(state.until)
        }

        delegate?.updateLockoutState(state)
    }
}

package io.horizontalsystems.bankwallet.modules.pin.unlock

import android.support.v4.hardware.fingerprint.FingerprintManagerCompat
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.core.IKeyStoreSafeExecute
import io.horizontalsystems.bankwallet.core.factories.LockoutUntilDateFactory
import io.horizontalsystems.bankwallet.core.managers.CurrentDateProvider
import io.horizontalsystems.bankwallet.core.managers.LockoutManager
import io.horizontalsystems.bankwallet.core.managers.OneTimeTimer
import io.horizontalsystems.bankwallet.core.managers.UptimeProvider
import io.horizontalsystems.bankwallet.entities.LockoutState
import io.horizontalsystems.bankwallet.modules.pin.PinViewModel

object UnlockPinModule {
    interface IUnlockPinRouter {
        fun dismiss(didUnlock: Boolean)
        fun closeApplication()
    }

    interface IUnlockPinInteractor {
        fun updateLockoutState()
        fun cacheSecuredData()
        fun unlock(pin: String): Boolean
        fun isBiometricOn(): Boolean
        fun onUnlock()
    }

    interface IUnlockPinInteractorDelegate {
        fun didBiometricUnlock()
        fun unlock()
        fun wrongPinSubmitted()
        fun setCryptoObject(cryptoObject: FingerprintManagerCompat.CryptoObject)
        fun updateLockoutState(state: LockoutState)
    }

    fun init(view: PinViewModel, router: IUnlockPinRouter, keystoreSafeExecute: IKeyStoreSafeExecute) {

        val lockoutManager = LockoutManager(App.localStorage, UptimeProvider(), LockoutUntilDateFactory(CurrentDateProvider()))
        val timer = OneTimeTimer()
        val interactor = UnlockPinInteractor(keystoreSafeExecute, App.localStorage, App.authManager, App.pinManager, App.lockManager, App.encryptionManager, lockoutManager, timer)
        val presenter = UnlockPinPresenter(interactor, router)

        view.delegate = presenter
        presenter.view = view
        interactor.delegate = presenter
    }
}

package io.horizontalsystems.bankwallet.modules.pin.unlock

import android.support.v4.hardware.fingerprint.FingerprintManagerCompat
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.entities.LockoutState
import io.horizontalsystems.bankwallet.modules.pin.PinModule
import io.horizontalsystems.bankwallet.modules.pin.PinPage

class UnlockPinPresenter(
        private val interactor: UnlockPinModule.IUnlockPinInteractor,
        private val router: UnlockPinModule.IUnlockPinRouter) : PinModule.IPinViewDelegate, UnlockPinModule.IUnlockPinInteractorDelegate {

    private val unlockPageIndex = 0
    private var enteredPin = ""
    private var cryptoObject: FingerprintManagerCompat.CryptoObject? = null
    var view: PinModule.IPinView? = null

    override fun viewDidLoad() {
        interactor.cacheSecuredData()
        view?.hideToolbar()
        view?.addPages(listOf(PinPage(R.string.Unlock_Page_EnterYourPin)))

        interactor.updateLockoutState()
    }

    override fun onEnter(pin: String, pageIndex: Int) {
        if (enteredPin.length < PinModule.PIN_COUNT) {
            enteredPin += pin
            view?.fillCircles(enteredPin.length, pageIndex)

            if (enteredPin.length == PinModule.PIN_COUNT) {
                if (interactor.unlock(enteredPin)) {
                    router.dismiss(true)
                } else {
                    wrongPinSubmitted()
                }
                enteredPin = ""
            }
        }
    }

    override fun onCancel() {
        router.dismiss(false)
    }

    override fun resetPin() {
        enteredPin = ""
    }

    override fun onDelete(pageIndex: Int) {
        if (enteredPin.isNotEmpty()) {
            enteredPin = enteredPin.substring(0, enteredPin.length - 1)
            view?.fillCircles(enteredPin.length, pageIndex)
        }
    }

    override fun didBiometricUnlock() {
        router.dismiss(true)
    }

    override fun unlock() {
        router.dismiss(true)
    }

    override fun setCryptoObject(cryptoObject: FingerprintManagerCompat.CryptoObject) {
        this.cryptoObject = cryptoObject
        showBiometricUnlock()
    }

    override fun wrongPinSubmitted() {
        view?.showPinWrong(unlockPageIndex)
    }

    override fun showBiometricUnlock() {
        if (interactor.isBiometricOn()) {
            cryptoObject?.let { view?.showFingerprintDialog(it) }
        }
    }

    override fun onBiometricUnlock() {
        interactor.onUnlock()
    }

    override fun updateLockoutState(state: LockoutState) {
        when (state) {
            is LockoutState.Unlocked -> view?.showAttemptsLeft(state.attemptsLeft, unlockPageIndex)
            is LockoutState.Locked -> view?.showLockView(state.until)
        }
    }

    override fun onBackPressed() {
        router.closeApplication()
    }
}

package io.horizontalsystems.bankwallet.modules.receive.viewitems

import io.horizontalsystems.bankwallet.modules.transactions.CoinCode

data class AddressItem(var address: String, var coinCode: CoinCode, var coinTitle: String)

package io.horizontalsystems.bankwallet.modules.settings.basecurrency

import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import io.horizontalsystems.bankwallet.BaseActivity
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.core.setOnSingleClickListener
import io.horizontalsystems.bankwallet.ui.view.ViewHolderProgressbar
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.activity_currency_switcher.*
import kotlinx.android.synthetic.main.view_holder_currency_item.*

class BaseCurrencySettingsActivity : BaseActivity(), CurrencySwitcherAdapter.Listener {

    private lateinit var viewModel: BaseCurrencySettingsViewModel
    private var adapter: CurrencySwitcherAdapter? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = ViewModelProviders.of(this).get(BaseCurrencySettingsViewModel::class.java)
        viewModel.init()

        setContentView(R.layout.activity_currency_switcher)

        setSupportActionBar(toolbar)
        supportActionBar?.title = getString(R.string.SettingsCurrency_Title)
        supportActionBar?.setDisplayHomeAsUpEnabled(true)
        supportActionBar?.setHomeAsUpIndicator(R.drawable.back)

        adapter = CurrencySwitcherAdapter(this)
        recyclerView.adapter = adapter
        recyclerView.layoutManager = LinearLayoutManager(this)

        viewModel.currencyItems.observe(this, Observer { items ->
            items?.let {
                adapter?.items = it
                adapter?.notifyDataSetChanged()
            }
        })

        viewModel.closeLiveEvent.observe(this, Observer {
            finish()
        })
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            android.R.id.home -> {
                onBackPressed()
                return true
            }
        }
        return super.onOptionsItemSelected(item)
    }

    override fun onItemClick(item: CurrencyItem) {
        viewModel.delegate.didSelect(item)
    }
}

class CurrencySwitcherAdapter(private var listener: Listener) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    private val VIEW_TYPE_ITEM = 1
    private val VIEW_TYPE_LOADING = 2

    interface Listener {
        fun onItemClick(item: CurrencyItem)
    }

    var items = listOf<CurrencyItem>()

    override fun getItemCount() = if (items.isEmpty()) 1 else items.size

    override fun getItemViewType(position: Int): Int = if (items.isEmpty()) {
        VIEW_TYPE_LOADING
    } else {
        VIEW_TYPE_ITEM
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        return when (viewType) {
            VIEW_TYPE_ITEM -> ViewHolderCurrency(inflater.inflate(ViewHolderCurrency.layoutResourceId, parent, false))
            else -> ViewHolderProgressbar(inflater.inflate(ViewHolderProgressbar.layoutResourceId, parent, false))
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder) {
            is ViewHolderCurrency -> holder.bind(items[position]) { listener.onItemClick(items[position]) }
        }
    }

}

class ViewHolderCurrency(override val containerView: View) : RecyclerView.ViewHolder(containerView), LayoutContainer {

    fun bind(item: CurrencyItem, onClick: () -> (Unit)) {

        containerView.setOnSingleClickListener { onClick.invoke() }
        title.text = item.code
        subtitle.text = item.symbol
        checkmarkIcon.visibility = if (item.selected) View.VISIBLE else View.GONE
    }

    companion object {
        val layoutResourceId: Int
            get() = R.layout.view_holder_currency_item
    }

}

package io.horizontalsystems.bankwallet.modules.settings.basecurrency

import io.horizontalsystems.bankwallet.core.ICurrencyManager
import io.horizontalsystems.bankwallet.entities.Currency

class BaseCurrencySettingsInteractor(
        private val currencyManager: ICurrencyManager): BaseCurrencySettingsModule.IBaseCurrencySettingsInteractor {

    var delegate: BaseCurrencySettingsModule.IBaseCurrencySettingsInteractorDelegate? = null

    override val currencies: List<Currency>
        get() = currencyManager.currencies

    override val baseCurrency: Currency
        get() = currencyManager.baseCurrency

    override fun setBaseCurrency(code: String) {
        currencyManager.setBaseCurrency(code= code)
        delegate?.didSetBaseCurrency()
    }
}

package io.horizontalsystems.bankwallet.modules.settings.basecurrency

import android.content.Context
import android.content.Intent
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.entities.Currency

object BaseCurrencySettingsModule {

    interface IBaseCurrencySettingsView{
        fun show(items: List<CurrencyItem>)
        fun close()
    }

    interface IBaseCurrencySettingsViewDelegate {
        fun viewDidLoad()
        fun didSelect(item: CurrencyItem)
    }

    interface IBaseCurrencySettingsInteractor {
        val currencies: List<Currency>
        val baseCurrency: Currency
        fun setBaseCurrency(code: String)
    }

    interface IBaseCurrencySettingsInteractorDelegate {
        fun didSetBaseCurrency()
    }

    fun init(view: BaseCurrencySettingsViewModel) {
        val interactor = BaseCurrencySettingsInteractor(App.currencyManager)
        val presenter = BaseCurrencySettingsPresenter(interactor)

        view.delegate = presenter
        presenter.view = view
        interactor.delegate = presenter
    }

    fun start(context: Context) {
        val intent = Intent(context, BaseCurrencySettingsActivity::class.java)
        context.startActivity(intent)
    }

}

data class CurrencyItem (val code: String, val symbol: String, val selected: Boolean) {

    override fun equals(other: Any?): Boolean {
        if (other is CurrencyItem) {
            return code == other.code
        }

        return super.equals(other)
    }

    override fun hashCode(): Int {
        var result = code.hashCode()
        result = 31 * result + symbol.hashCode()
        result = 31 * result + selected.hashCode()
        return result
    }
}

package io.horizontalsystems.bankwallet.modules.settings.basecurrency

class BaseCurrencySettingsPresenter(private val interactor: BaseCurrencySettingsModule.IBaseCurrencySettingsInteractor) :
        BaseCurrencySettingsModule.IBaseCurrencySettingsViewDelegate,
        BaseCurrencySettingsModule.IBaseCurrencySettingsInteractorDelegate {

    var view: BaseCurrencySettingsModule.IBaseCurrencySettingsView? = null

    override fun viewDidLoad() {
        showItems()
    }

    override fun didSelect(item: CurrencyItem) {
        if (!item.selected) {
            interactor.setBaseCurrency(item.code)
        }
    }

    override fun didSetBaseCurrency() {
        view?.close()
    }

    private fun showItems() {
        val baseCurrencyCode = interactor.baseCurrency.code

        val items = interactor.currencies.map { CurrencyItem(it.code, it.symbol, it.code == baseCurrencyCode) }
        view?.show(items)
    }
}

package io.horizontalsystems.bankwallet.modules.settings.basecurrency

import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel
import io.horizontalsystems.bankwallet.SingleLiveEvent

class BaseCurrencySettingsViewModel: ViewModel(), BaseCurrencySettingsModule.IBaseCurrencySettingsView {

    lateinit var delegate: BaseCurrencySettingsModule.IBaseCurrencySettingsViewDelegate
    val currencyItems = MutableLiveData<List<CurrencyItem>>()
    val closeLiveEvent = SingleLiveEvent<Unit>()

    fun init() {
        BaseCurrencySettingsModule.init(this)
        delegate.viewDidLoad()
    }

    override fun show(items: List<CurrencyItem>) {
        currencyItems.value = items
    }

    override fun close() {
        closeLiveEvent.call()
    }
}

package io.horizontalsystems.bankwallet.modules.settings.language

import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.os.Bundle
import android.support.v7.widget.LinearLayoutManager
import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import io.horizontalsystems.bankwallet.BaseActivity
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.core.setOnSingleClickListener
import io.horizontalsystems.bankwallet.modules.main.MainActivity
import io.horizontalsystems.bankwallet.modules.main.MainModule
import io.horizontalsystems.bankwallet.ui.view.ViewHolderProgressbar
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.activity_language_settings.*
import kotlinx.android.synthetic.main.view_holder_language_item.*

class LanguageSettingsActivity: BaseActivity(), LanguageSettingsAdapter.Listener {

    private lateinit var viewModel: LanguageSettingsViewModel
    private var adapter: LanguageSettingsAdapter? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = ViewModelProviders.of(this).get(LanguageSettingsViewModel::class.java)
        viewModel.init()

        setContentView(R.layout.activity_language_settings)

        setSupportActionBar(toolbar)
        supportActionBar?.setDisplayHomeAsUpEnabled(true)
        supportActionBar?.setHomeAsUpIndicator(R.drawable.back)

        adapter = LanguageSettingsAdapter(this)
        recyclerView.adapter = adapter
        recyclerView.layoutManager = LinearLayoutManager(this)

        viewModel.languageItems.observe(this, Observer { items ->
            items?.let {
                adapter?.items = it
                adapter?.notifyDataSetChanged()
            }
        })

        viewModel.reloadAppLiveEvent.observe(this, Observer {
            MainModule.startAsNewTask(this, MainActivity.SETTINGS_TAB_POSITION)
        })

        viewModel.titleLiveDate.observe(this, Observer { title ->
            title?.let {
                supportActionBar?.title = getString(it)
            }
        })
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            android.R.id.home -> {
                onBackPressed()
                return true
            }
        }
        return super.onOptionsItemSelected(item)
    }

    override fun onItemClick(item: LanguageItem) {
        viewModel.delegate.didSelect(item)
    }
}

class LanguageSettingsAdapter(private var listener: Listener) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    private val VIEW_TYPE_ITEM = 1
    private val VIEW_TYPE_LOADING = 2

    interface Listener {
        fun onItemClick(item: LanguageItem)
    }

    var items = listOf<LanguageItem>()

    override fun getItemCount() = if (items.isEmpty()) 1 else items.size

    override fun getItemViewType(position: Int): Int = if(items.isEmpty()) {
        VIEW_TYPE_LOADING
    } else {
        VIEW_TYPE_ITEM
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        return when (viewType) {
            VIEW_TYPE_ITEM -> ViewHolderLanguageItem(inflater.inflate(ViewHolderLanguageItem.layoutResourceId, parent, false))
            else -> ViewHolderProgressbar(inflater.inflate(ViewHolderProgressbar.layoutResourceId, parent, false))
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (holder) {
            is ViewHolderLanguageItem -> holder.bind(items[position]) { listener.onItemClick(items[position]) }
        }
    }

}

class ViewHolderLanguageItem(override val containerView: View) : RecyclerView.ViewHolder(containerView), LayoutContainer {

    fun bind(item: LanguageItem, onClick: () -> (Unit)) {

        containerView.setOnSingleClickListener { onClick.invoke() }
        title.text = item.locale.getDisplayLanguage(item.locale).capitalize()
        subtitle.text = item.locale.displayName.capitalize()
        checkmarkIcon.visibility = if (item.current) View.VISIBLE else View.GONE
    }

    companion object {
        val layoutResourceId: Int
            get() = R.layout.view_holder_language_item
    }
}

package io.horizontalsystems.bankwallet.modules.settings.language

import io.horizontalsystems.bankwallet.core.ILanguageManager

class LanguageSettingsInteractor(private val languageManager: ILanguageManager): LanguageSettingsModule.ILanguageSettingsInteractor {

    var delegate: LanguageSettingsModule.ILanguageSettingsInteractorDelegate? = null

    override var items: List<LanguageItem> = listOf()
        get() {
            val currentLanguage = languageManager.currentLanguage
            return languageManager.availableLanguages.map { locale ->
                LanguageItem(locale, currentLanguage.language == locale.language)
            }
        }

    override fun setCurrentLanguage(item: LanguageItem) {
        languageManager.currentLanguage = item.locale
        delegate?.didSetCurrentLanguage()
    }
}

package io.horizontalsystems.bankwallet.modules.settings.language

import android.content.Context
import android.content.Intent
import io.horizontalsystems.bankwallet.core.App
import java.util.*

object LanguageSettingsModule {

    interface ILanguageSettingsView {
        fun setTitle(title: Int)
        fun show(items: List<LanguageItem>)
    }

    interface ILanguageSettingsViewDelegate {
        fun viewDidLoad()
        fun didSelect(item: LanguageItem)
    }

    interface ILanguageSettingsInteractor {
        var items: List<LanguageItem>
        fun setCurrentLanguage(item: LanguageItem)
    }

    interface ILanguageSettingsInteractorDelegate {
        fun didSetCurrentLanguage()
    }

    interface ILanguageSettingsRouter{
        fun reloadAppInterface()
    }

    fun start(context: Context) {
        val intent = Intent(context, LanguageSettingsActivity::class.java)
        context.startActivity(intent)
    }

    fun init(view: LanguageSettingsViewModel, router: ILanguageSettingsRouter) {
        val interactor = LanguageSettingsInteractor(languageManager = App.languageManager)
        val presenter = LanguageSettingsPresenter(router, interactor)

        view.delegate = presenter
        presenter.view = view
        interactor.delegate = presenter
    }
}

data class LanguageItem(val locale: Locale, var current: Boolean)

package io.horizontalsystems.bankwallet.modules.settings.language

import io.horizontalsystems.bankwallet.R

class LanguageSettingsPresenter(
        private val router: LanguageSettingsModule.ILanguageSettingsRouter,
        private val interactor: LanguageSettingsModule.ILanguageSettingsInteractor)
    : LanguageSettingsModule.ILanguageSettingsViewDelegate, LanguageSettingsModule.ILanguageSettingsInteractorDelegate {

    var view: LanguageSettingsModule.ILanguageSettingsView? = null

    override fun didSetCurrentLanguage() {
        router.reloadAppInterface()
    }

    override fun viewDidLoad() {
        view?.setTitle(R.string.SettingsLanguage_Title)
        view?.show(items = interactor.items)
    }

    override fun didSelect(item: LanguageItem) {
        interactor.setCurrentLanguage(item)
    }

}

package io.horizontalsystems.bankwallet.modules.settings.language

import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel
import io.horizontalsystems.bankwallet.SingleLiveEvent

class LanguageSettingsViewModel: ViewModel(), LanguageSettingsModule.ILanguageSettingsView, LanguageSettingsModule.ILanguageSettingsRouter {

    lateinit var delegate: LanguageSettingsModule.ILanguageSettingsViewDelegate
    val languageItems = MutableLiveData<List<LanguageItem>>()
    val titleLiveDate = MutableLiveData<Int>()
    val reloadAppLiveEvent = SingleLiveEvent<Unit>()

    fun init() {
        LanguageSettingsModule.init(this, this)
        delegate.viewDidLoad()
    }

    override fun setTitle(title: Int) {
        titleLiveDate.value = title
    }

    override fun show(items: List<LanguageItem>) {
        languageItems.value = items
    }

    override fun reloadAppInterface() {
        reloadAppLiveEvent.call()
    }
}

package io.horizontalsystems.bankwallet.modules.settings.main

import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CompoundButton
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.modules.main.MainActivity
import io.horizontalsystems.bankwallet.modules.main.MainModule
import io.horizontalsystems.bankwallet.modules.settings.AboutSettingsActivity
import io.horizontalsystems.bankwallet.modules.settings.basecurrency.BaseCurrencySettingsModule
import io.horizontalsystems.bankwallet.modules.settings.language.LanguageSettingsModule
import io.horizontalsystems.bankwallet.modules.settings.security.SecuritySettingsModule
import io.horizontalsystems.bankwallet.viewHelpers.LayoutHelper
import kotlinx.android.synthetic.main.fragment_settings.*

class MainSettingsFragment : android.support.v4.app.Fragment() {
    private lateinit var viewModel: MainSettingsViewModel

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.fragment_settings, container, false);
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewModel = ViewModelProviders.of(this).get(MainSettingsViewModel::class.java)
        viewModel.init()

        securityCenter.setOnClickListener {
            viewModel.delegate.didTapSecurity()
        }

        baseCurrency.apply {
            setOnClickListener {
                viewModel.delegate.didTapBaseCurrency()
            }
        }

        language.apply {
            setOnClickListener {
                viewModel.delegate.didTapLanguage()
            }
        }

        lightMode.apply {
            setOnClickListener {
                switchToggle()
            }
        }

        companyLogo.setOnClickListener {
            viewModel.delegate.didTapAppLink()
        }

        about.setOnClickListener {
            viewModel.delegate.didTapAbout()
        }

        viewModel.titleLiveDate.observe(this, Observer { title ->
            title?.let { toolbar.setTitle(it) }
        })

        viewModel.baseCurrencyLiveDate.observe(this, Observer { currency ->
            currency?.let {
                baseCurrency.selectedValue = it
            }
        })

        viewModel.backedUpLiveDate.observe(this, Observer { backedUp ->
            backedUp?.let {
                securityCenter.badge = LayoutHelper.getInfoBadge(it, resources)
            }
        })

        viewModel.showBaseCurrencySettingsLiveEvent.observe(this, Observer {
            context?.let { context -> BaseCurrencySettingsModule.start(context) }
        })

        viewModel.languageLiveDate.observe(this, Observer { languageCode ->
            languageCode?.let {
                language.selectedValue = it.capitalize()
            }
        })

        viewModel.lightModeLiveDate.observe(this, Observer { lightModeValue ->
            lightModeValue?.let {
                lightMode.apply {
                    switchIsChecked = it

                    switchOnCheckedChangeListener = CompoundButton.OnCheckedChangeListener { _, isChecked ->
                        viewModel.delegate.didSwitchLightMode(isChecked)
                    }
                }
            }
        })

        viewModel.showLanguageSettingsLiveEvent.observe(this, Observer {
            context?.let { context -> LanguageSettingsModule.start(context) }
        })

        viewModel.showAboutLiveEvent.observe(this, Observer {
            activity?.let {
                AboutSettingsActivity.start(it)
            }
        })

        viewModel.showSecuritySettingsLiveEvent.observe(this, Observer {
            context?.let {
                SecuritySettingsModule.start(it)
            }
        })

        viewModel.tabItemBadgeLiveDate.observe(this, Observer { count ->
            activity?.let { activity ->
                count?.let {
                    (activity as? MainActivity)?.updateSettingsTabCounter(it)
                }
            }
        })

        viewModel.appVersionLiveDate.observe(this, Observer { version ->
            version?.let { appName.text = getString(R.string.Settings_InfoTitleWithVersion, it) }
        })

        viewModel.showAppLinkLiveEvent.observe(this, Observer {
            val uri = Uri.parse(getString(R.string.Settings_InfoLink))
            val intent = Intent(Intent.ACTION_VIEW, uri)
            activity?.startActivity(intent)
        })

        viewModel.reloadAppLiveEvent.observe(this, Observer {
            context?.let { context -> MainModule.startAsNewTask(context, MainActivity.SETTINGS_TAB_POSITION) }
        })

    }

}

package io.horizontalsystems.bankwallet.modules.settings.main

import io.horizontalsystems.bankwallet.core.*
import io.reactivex.disposables.CompositeDisposable

class MainSettingsInteractor(
        private val localStorage: ILocalStorage,
        private val wordsManager: IWordsManager,
        languageManager: ILanguageManager,
        systemInfoManager: ISystemInfoManager,
        private val currencyManager: ICurrencyManager): MainSettingsModule.IMainSettingsInteractor {

    private var disposables: CompositeDisposable = CompositeDisposable()

    var delegate: MainSettingsModule.IMainSettingsInteractorDelegate? = null

    init {
        disposables.add(wordsManager.backedUpSignal.subscribe {
            onUpdateBackedUp()
        })

        disposables.add(currencyManager.baseCurrencyUpdatedSignal.subscribe {
            delegate?.didUpdateBaseCurrency(currencyManager.baseCurrency.code)
        })
    }

    private fun onUpdateBackedUp() {
        if (wordsManager.isBackedUp) {
            delegate?.didBackup()
        }
    }

    override var isBackedUp: Boolean = wordsManager.isBackedUp

    override var currentLanguage: String = languageManager.currentLanguage.displayLanguage

    override val baseCurrency: String
        get() = currencyManager.baseCurrency.code

    override var appVersion: String = systemInfoManager.appVersion

    override fun getLightMode(): Boolean {
        return localStorage.isLightModeOn
    }

    override fun setLightMode(lightMode: Boolean) {
        localStorage.isLightModeOn = lightMode
        delegate?.didUpdateLightMode()
    }

}

package io.horizontalsystems.bankwallet.modules.settings.main

import io.horizontalsystems.bankwallet.core.App

object MainSettingsModule {

    interface IMainSettingsView {
        fun setTitle(title: Int)
        fun setBackedUp(backedUp: Boolean)
        fun setBaseCurrency(currency: String)
        fun setLanguage(language: String)
        fun setLightMode(lightMode: Boolean)
        fun setAppVersion(appVersion: String)
        fun setTabItemBadge(count: Int)
    }

    interface IMainSettingsViewDelegate {
        fun viewDidLoad()
        fun didTapSecurity()
        fun didTapBaseCurrency()
        fun didTapLanguage()
        fun didSwitchLightMode(lightMode: Boolean)
        fun didTapAbout()
        fun didTapAppLink()
    }

    interface IMainSettingsInteractor {
        var isBackedUp: Boolean
        var currentLanguage: String
        val baseCurrency: String
        var appVersion: String
        fun getLightMode(): Boolean
        fun setLightMode(lightMode: Boolean)
    }

    interface IMainSettingsInteractorDelegate {
        fun didBackup()
        fun didUpdateBaseCurrency(baseCurrency: String)
        fun didUpdateLightMode()
    }

    interface IMainSettingsRouter{
        fun showSecuritySettings()
        fun showBaseCurrencySettings()
        fun showLanguageSettings()
        fun showAbout()
        fun openAppLink()
        fun reloadAppInterface()
    }

    fun init(view: MainSettingsViewModel, router: IMainSettingsRouter) {
        val interactor = MainSettingsInteractor(
                localStorage = App.localStorage,
                wordsManager = App.wordsManager,
                languageManager = App.languageManager,
                systemInfoManager = App.systemInfoManager,
                currencyManager = App.currencyManager
        )

        val presenter = MainSettingsPresenter(router, interactor)

        view.delegate = presenter
        presenter.view = view
        interactor.delegate = presenter
    }
}

package io.horizontalsystems.bankwallet.modules.settings.main

import io.horizontalsystems.bankwallet.R

class MainSettingsPresenter(
        private val router: MainSettingsModule.IMainSettingsRouter,
        private val interactor: MainSettingsModule.IMainSettingsInteractor)
    : MainSettingsModule.IMainSettingsViewDelegate, MainSettingsModule.IMainSettingsInteractorDelegate {

    var view: MainSettingsModule.IMainSettingsView? = null

    override fun viewDidLoad() {
        view?.setTitle(R.string.Settings_Title)
        view?.setBackedUp(interactor.isBackedUp)
        view?.setBaseCurrency(interactor.baseCurrency)
        view?.setLanguage(interactor.currentLanguage)
        view?.setLightMode(interactor.getLightMode())
        view?.setAppVersion(interactor.appVersion)

        view?.setTabItemBadge(if (interactor.isBackedUp) 0 else 1)
    }

    override fun didTapSecurity() {
        router.showSecuritySettings()
    }

    override fun didTapBaseCurrency() {
        router.showBaseCurrencySettings()
    }

    override fun didTapLanguage() {
        router.showLanguageSettings()
    }

    override fun didSwitchLightMode(lightMode: Boolean) {
        interactor.setLightMode(lightMode)
    }

    override fun didTapAbout() {
        router.showAbout()
    }

    override fun didTapAppLink() {
        router.openAppLink()
    }

    override fun didBackup() {
        view?.setBackedUp(true)
        view?.setTabItemBadge(0)
    }

    override fun didUpdateBaseCurrency(baseCurrency: String) {
        view?.setBaseCurrency(baseCurrency)
    }

    override fun didUpdateLightMode() {
        router.reloadAppInterface()
    }
}

package io.horizontalsystems.bankwallet.modules.settings.main

import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel
import io.horizontalsystems.bankwallet.SingleLiveEvent

class MainSettingsViewModel: ViewModel(), MainSettingsModule.IMainSettingsView, MainSettingsModule.IMainSettingsRouter {

    lateinit var delegate: MainSettingsModule.IMainSettingsViewDelegate

    val titleLiveDate = MutableLiveData<Int>()
    val backedUpLiveDate = MutableLiveData<Boolean>()
    val baseCurrencyLiveDate = MutableLiveData<String>()
    val languageLiveDate = MutableLiveData<String>()
    val lightModeLiveDate = MutableLiveData<Boolean>()
    val appVersionLiveDate = MutableLiveData<String>()
    val tabItemBadgeLiveDate = MutableLiveData<Int>()

    val showSecuritySettingsLiveEvent = SingleLiveEvent<Unit>()
    val showBaseCurrencySettingsLiveEvent = SingleLiveEvent<Unit>()
    val showLanguageSettingsLiveEvent = SingleLiveEvent<Unit>()
    val showAboutLiveEvent = SingleLiveEvent<Unit>()
    val showAppLinkLiveEvent = SingleLiveEvent<Unit>()
    val reloadAppLiveEvent = SingleLiveEvent<Unit>()

    fun init() {
        MainSettingsModule.init(this, this)
        delegate.viewDidLoad()
    }

    override fun setTitle(title: Int) {
        titleLiveDate.value = title
    }

    override fun setBackedUp(backedUp: Boolean) {
        backedUpLiveDate.value = backedUp
    }

    override fun setBaseCurrency(currency: String) {
        baseCurrencyLiveDate.value = currency
    }

    override fun setLanguage(language: String) {
        languageLiveDate.value = language
    }

    override fun setLightMode(lightMode: Boolean) {
        lightModeLiveDate.value = lightMode
    }

    override fun setAppVersion(appVersion: String) {
        appVersionLiveDate.value = appVersion
    }

    override fun setTabItemBadge(count: Int) {
        tabItemBadgeLiveDate.value = count
    }

    override fun showSecuritySettings() {
        showSecuritySettingsLiveEvent.call()
    }

    override fun showBaseCurrencySettings() {
        showBaseCurrencySettingsLiveEvent.call()
    }

    override fun showLanguageSettings() {
        showLanguageSettingsLiveEvent.call()
    }

    override fun showAbout() {
        showAboutLiveEvent.call()
    }

    override fun openAppLink() {
        showAppLinkLiveEvent.call()
    }

    override fun reloadAppInterface() {
        reloadAppLiveEvent.call()
    }

}

package io.horizontalsystems.bankwallet.modules.settings.security

import android.arch.lifecycle.Observer
import android.arch.lifecycle.ViewModelProviders
import android.content.Intent
import android.os.Bundle
import android.view.MenuItem
import android.view.View
import android.widget.CompoundButton
import io.horizontalsystems.bankwallet.BaseActivity
import io.horizontalsystems.bankwallet.LauncherActivity
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.entities.BiometryType
import io.horizontalsystems.bankwallet.lib.AlertDialogFragment
import io.horizontalsystems.bankwallet.modules.backup.BackupModule
import io.horizontalsystems.bankwallet.modules.backup.BackupPresenter
import io.horizontalsystems.bankwallet.modules.pin.PinModule
import io.horizontalsystems.bankwallet.modules.restore.RestoreModule
import io.horizontalsystems.bankwallet.ui.dialogs.BottomButtonColor
import io.horizontalsystems.bankwallet.ui.dialogs.BottomConfirmAlert
import io.horizontalsystems.bankwallet.viewHelpers.LayoutHelper
import kotlinx.android.synthetic.main.activity_settings_security.*

class SecuritySettingsActivity : BaseActivity(), BottomConfirmAlert.Listener {

    private lateinit var viewModel: SecuritySettingsViewModel

    enum class Action {
        OPEN_RESTORE,
        CLEAR_WALLETS
    }

    private var selectedAction: Action? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        viewModel = ViewModelProviders.of(this).get(SecuritySettingsViewModel::class.java)
        viewModel.init()

        setContentView(R.layout.activity_settings_security)

        setSupportActionBar(toolbar)
        supportActionBar?.setDisplayHomeAsUpEnabled(true)
        supportActionBar?.setHomeAsUpIndicator(R.drawable.back)

        changePin.setOnClickListener { viewModel.delegate.didTapEditPin() }

        backupWallet.setOnClickListener { viewModel.delegate.didTapBackupWallet() }

        importWallet.setOnClickListener {
            selectedAction = Action.OPEN_RESTORE
            val confirmationList = mutableListOf(
                    R.string.SettingsSecurity_ImportWalletConfirmation_1,
                    R.string.SettingsSecurity_ImportWalletConfirmation_2
            )
            BottomConfirmAlert.show(this, confirmationList, this)
        }

        unlink.setOnClickListener {
            selectedAction = Action.CLEAR_WALLETS
            val confirmationList = mutableListOf(
                    R.string.SettingsSecurity_ImportWalletConfirmation_1,
                    R.string.SettingsSecurity_ImportWalletConfirmation_2
            )
            BottomConfirmAlert.show(this, confirmationList, this, BottomButtonColor.RED)
        }

        unlink.titleTextColor = R.color.red_warning

        viewModel.backedUpLiveData.observe(this, Observer { wordListBackedUp ->
            wordListBackedUp?.let {
                backupWallet.badge = LayoutHelper.getInfoBadge(it, resources)
            }
        })

        viewModel.openEditPinLiveEvent.observe(this, Observer {
//            PinModule.startForEditPinAuth(this)
            PinModule.startForEditPin(this)
        })

        viewModel.openRestoreWalletLiveEvent.observe(this, Observer {
            RestoreModule.start(this)
        })

        viewModel.openBackupWalletLiveEvent.observe(this, Observer {
            BackupModule.start(this@SecuritySettingsActivity, BackupPresenter.DismissMode.DISMISS_SELF)
        })

        viewModel.titleLiveDate.observe(this, Observer { title ->
            title?.let { supportActionBar?.title = getString(it) }
        })

        viewModel.biometryTypeLiveDate.observe(this, Observer { biometryType ->
            fingerprint.visibility = if (biometryType == BiometryType.FINGER) View.VISIBLE else View.GONE
        })

        viewModel.biometricUnlockOnLiveDate.observe(this, Observer { switchIsOn ->
            switchIsOn?.let { switchOn ->
                fingerprint.apply {
                    switchIsChecked = switchOn
                    setOnClickListener {
                        if (App.localStorage.isBiometricOn || fingerprintCanBeEnabled()) {
                            switchToggle()
                        }
                    }

                    switchOnCheckedChangeListener = CompoundButton.OnCheckedChangeListener { _, isChecked ->
                        App.localStorage.isBiometricOn = isChecked
                    }
                }
            }
        })

        viewModel.reloadAppLiveEvent.observe(this, Observer {
            val intent = Intent(this, LauncherActivity::class.java)
            intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
            startActivity(intent)
        })

    }

    override fun onConfirmationSuccess() {
        when (selectedAction) {
            Action.OPEN_RESTORE -> viewModel.delegate.didTapRestoreWallet()
            Action.CLEAR_WALLETS -> viewModel.delegate.confirmedUnlinkWallet()
        }
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            android.R.id.home -> {
                onBackPressed()
                return true
            }
        }
        return super.onOptionsItemSelected(item)
    }

    private fun fingerprintCanBeEnabled(): Boolean {
        val touchSensorCanBeUsed = App.systemInfoManager.touchSensorCanBeUsed()
        if (!touchSensorCanBeUsed) {
            AlertDialogFragment.newInstance(R.string.Settings_Error_FingerprintNotEnabled, R.string.Settings_Error_NoFingerprintAddedYet, R.string.Alert_Ok)
                    .show(this.supportFragmentManager, "fingerprint_not_enabled_alert")
            return false
        }
        return true
    }
}

package io.horizontalsystems.bankwallet.modules.settings.security

import io.horizontalsystems.bankwallet.core.ILocalStorage
import io.horizontalsystems.bankwallet.core.ISystemInfoManager
import io.horizontalsystems.bankwallet.core.IWordsManager
import io.horizontalsystems.bankwallet.core.managers.AuthManager
import io.horizontalsystems.bankwallet.entities.BiometryType

class SecuritySettingsInteractor(
        private val authManager: AuthManager,
        private val wordsManager: IWordsManager,
        private val localStorage: ILocalStorage,
        private val systemInfoManager: ISystemInfoManager): SecuritySettingsModule.ISecuritySettingsInteractor {

    var delegate: SecuritySettingsModule.ISecuritySettingsInteractorDelegate? = null

    init {
        wordsManager.backedUpSignal.subscribe {
            onUpdateBackedUp()
        }
    }

    private fun onUpdateBackedUp() {
        if (wordsManager.isBackedUp) {
            delegate?.didBackup()
        }
    }

    override var biometryType: BiometryType = BiometryType.NONE
        get() = systemInfoManager.biometryType

    override var isBackedUp: Boolean = wordsManager.isBackedUp

    override fun getBiometricUnlockOn(): Boolean {
        return localStorage.isBiometricOn
    }

    override fun setBiometricUnlockOn(biometricUnlockOn: Boolean) {
        localStorage.isBiometricOn = biometricUnlockOn
    }

    override fun unlinkWallet() {
        authManager.logout()
        delegate?.didUnlinkWallet()
    }
}

package io.horizontalsystems.bankwallet.modules.settings.security

import android.content.Context
import android.content.Intent
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.entities.BiometryType

object SecuritySettingsModule {

    interface ISecuritySettingsView {
        fun setTitle(title: Int)
        fun setBiometricUnlockOn(biometricUnlockOn: Boolean)
        fun setBiometryType(biometryType: BiometryType)
        fun setBackedUp(backedUp: Boolean)
        fun reloadApp()
    }

    interface ISecuritySettingsViewDelegate {
        fun viewDidLoad()
        fun didSwitchBiometricUnlock(biometricUnlockOn: Boolean)
        fun didTapEditPin()
        fun didTapBackupWallet()
        fun didTapRestoreWallet()
        fun confirmedUnlinkWallet()
    }

    interface ISecuritySettingsInteractor {
        var isBackedUp: Boolean
        var biometryType: BiometryType
        fun getBiometricUnlockOn(): Boolean
        fun setBiometricUnlockOn(biometricUnlockOn: Boolean)
        fun unlinkWallet()
    }

    interface ISecuritySettingsInteractorDelegate {
        fun didBackup()
        fun didUnlinkWallet()
    }

    interface ISecuritySettingsRouter{
        fun showEditPin()
        fun showBackupWallet()
        fun showRestoreWallet()
    }

    fun start(context: Context) {
        val intent = Intent(context, SecuritySettingsActivity::class.java)
        context.startActivity(intent)
    }

    fun init(view: SecuritySettingsViewModel, router: ISecuritySettingsRouter) {
        val interactor = SecuritySettingsInteractor(App.authManager, App.wordsManager, App.localStorage, App.systemInfoManager)
        val presenter = SecuritySettingsPresenter(router, interactor)

        view.delegate = presenter
        presenter.view = view
        interactor.delegate = presenter
    }
}

package io.horizontalsystems.bankwallet.modules.settings.security

import io.horizontalsystems.bankwallet.R

class SecuritySettingsPresenter(
        private val router: SecuritySettingsModule.ISecuritySettingsRouter,
        private val interactor: SecuritySettingsModule.ISecuritySettingsInteractor)
    : SecuritySettingsModule.ISecuritySettingsViewDelegate, SecuritySettingsModule.ISecuritySettingsInteractorDelegate {

    var view: SecuritySettingsModule.ISecuritySettingsView? = null

    override fun viewDidLoad() {
        view?.setTitle(R.string.Settings_SecurityCenter)
        view?.setBiometricUnlockOn(interactor.getBiometricUnlockOn())
        view?.setBiometryType(interactor.biometryType)
        view?.setBackedUp(interactor.isBackedUp)
    }

    override fun didSwitchBiometricUnlock(biometricUnlockOn: Boolean) {
        interactor.setBiometricUnlockOn(biometricUnlockOn)
    }

    override fun didTapEditPin() {
        router.showEditPin()
    }

    override fun didTapBackupWallet() {
        router.showBackupWallet()
    }

    override fun didTapRestoreWallet() {
        router.showRestoreWallet()
    }

    override fun confirmedUnlinkWallet() {
        interactor.unlinkWallet()
    }

    override fun didBackup() {
        view?.setBackedUp(true)
    }

    override fun didUnlinkWallet() {
        view?.reloadApp()
    }
}

package io.horizontalsystems.bankwallet.modules.settings.security

import android.arch.lifecycle.MutableLiveData
import android.arch.lifecycle.ViewModel
import io.horizontalsystems.bankwallet.SingleLiveEvent
import io.horizontalsystems.bankwallet.entities.BiometryType

class SecuritySettingsViewModel: ViewModel(), SecuritySettingsModule.ISecuritySettingsView, SecuritySettingsModule.ISecuritySettingsRouter {

    lateinit var delegate: SecuritySettingsModule.ISecuritySettingsViewDelegate

    val titleLiveDate = MutableLiveData<Int>()
    val biometryTypeLiveDate = MutableLiveData<BiometryType>()

    val backedUpLiveData = MutableLiveData<Boolean>()
    val biometricUnlockOnLiveDate = MutableLiveData<Boolean>()
    val openEditPinLiveEvent = SingleLiveEvent<Unit>()
    val openBackupWalletLiveEvent = SingleLiveEvent<Unit>()
    val openRestoreWalletLiveEvent = SingleLiveEvent<Unit>()
    val reloadAppLiveEvent = SingleLiveEvent<Unit>()

    fun init() {
        SecuritySettingsModule.init(this, this)
        delegate.viewDidLoad()
    }

    override fun setTitle(title: Int) {
        titleLiveDate.value = title
    }

    override fun setBiometricUnlockOn(biometricUnlockOn: Boolean) {
        biometricUnlockOnLiveDate.value = biometricUnlockOn
    }

    override fun setBiometryType(biometryType: BiometryType) {
        biometryTypeLiveDate.value = biometryType
    }

    override fun setBackedUp(backedUp: Boolean) {
        backedUpLiveData.value = backedUp
    }

    override fun showEditPin() {
        openEditPinLiveEvent.call()
    }

    override fun showBackupWallet() {
        openBackupWalletLiveEvent.call()
    }

    override fun showRestoreWallet() {
        openRestoreWalletLiveEvent.call()
    }

    override fun reloadApp() {
        reloadAppLiveEvent.call()
    }
}

package io.horizontalsystems.bankwallet.modules.transactions.transactionInfo

import io.horizontalsystems.bankwallet.core.IClipboardManager

class TransactionInfoInteractor(private var clipboardManager: IClipboardManager) : TransactionInfoModule.Interactor {
    var delegate: TransactionInfoModule.InteractorDelegate? = null

    override fun onCopy(value: String) {
        clipboardManager.copyText(value)
    }

}

package io.horizontalsystems.bankwallet.modules.transactions.transactionInfo

import android.content.Context
import android.support.constraint.ConstraintLayout
import android.util.AttributeSet
import android.view.View
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.modules.transactions.TransactionStatus
import kotlinx.android.synthetic.main.view_transaction_info_item.view.*

class TransactionInfoItemView : ConstraintLayout {

    private var attrTitle: String? = null
    private var attrValue: String? = null
    private var attrValueSubtitle: String? = null
    private var attrShowValueBackground: String? = null
    private var attrValueIcon: String? = null

    constructor(context: Context) : super(context) {
        initializeViews()
    }

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
        initializeViews()
        loadAttributes(attrs)
    }

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        initializeViews()
        loadAttributes(attrs)
    }

    private fun initializeViews() {
        ConstraintLayout.inflate(context, R.layout.view_transaction_info_item, this)
    }

    fun bindAddress(title: String? = null, address: String? = null, showBottomBorder: Boolean = false) {
        txtTitle.text = title
        address?.let { addressView.bind(it) }
        addressView.visibility = if (address == null) View.GONE else View.VISIBLE
        border.visibility = if (showBottomBorder) View.VISIBLE else View.GONE

        invalidate()
    }

    fun bindTime(title: String? = null, time: String? = null) {
        txtTitle.text = title
        valueText.text = time
        valueText.visibility = if (time == null) View.GONE else View.VISIBLE
        border.visibility = View.VISIBLE
    }

    fun bindStatus(transactionStatus: TransactionStatus) {
        transactionStatusView.visibility = View.VISIBLE
        border.visibility = View.VISIBLE
        txtTitle.setText(R.string.TransactionInfo_Status)
        transactionStatusView.bind(transactionStatus)
        invalidate()
    }

    private fun loadAttributes(attrs: AttributeSet) {
        val ta = context.obtainStyledAttributes(attrs, R.styleable.TransactionInfoItemView, 0, 0)
        try {
            attrTitle = ta.getString(R.styleable.TransactionInfoItemView_title)
            attrValue = ta.getString(R.styleable.TransactionInfoItemView_value)
            attrValueSubtitle = ta.getString(R.styleable.TransactionInfoItemView_valueSubtitle)
            attrShowValueBackground = ta.getString(R.styleable.TransactionInfoItemView_showValueBackground)
            attrValueIcon = ta.getString(R.styleable.TransactionInfoItemView_valueIcon)
        } finally {
            ta.recycle()
        }
    }

}

package io.horizontalsystems.bankwallet.modules.transactions.transactionInfo

import io.horizontalsystems.bankwallet.modules.transactions.CoinCode
import io.horizontalsystems.bankwallet.viewHelpers.TextHelper

object TransactionInfoModule {
    interface View {
        fun showCopied()
    }

    interface ViewDelegate {
        fun onCopy(value: String)
        fun openFullInfo(transactionHash: String, coinCode: CoinCode)
    }

    interface Interactor {
        fun onCopy(value: String)
    }

    interface InteractorDelegate

    interface Router {
        fun openFullInfo(transactionHash: String, coinCode: CoinCode)
    }

    fun init(view: TransactionInfoViewModel, router: Router) {
        val interactor = TransactionInfoInteractor(TextHelper)
        val presenter = TransactionInfoPresenter(interactor, router)

        view.delegate = presenter
        presenter.view = view
        interactor.delegate = presenter
    }

}

package io.horizontalsystems.bankwallet.modules.transactions.transactionInfo

import io.horizontalsystems.bankwallet.modules.transactions.CoinCode

class TransactionInfoPresenter(
        private val interactor: TransactionInfoModule.Interactor,
        private val router: TransactionInfoModule.Router
) : TransactionInfoModule.ViewDelegate, TransactionInfoModule.InteractorDelegate {

    var view: TransactionInfoModule.View? = null

    // ViewDelegate methods

    override fun onCopy(value: String) {
        interactor.onCopy(value)
        view?.showCopied()
    }

    override fun openFullInfo(transactionHash: String, coinCode: CoinCode) {
        router.openFullInfo(transactionHash, coinCode)
    }

}

package io.horizontalsystems.bankwallet.modules.transactions.transactionInfo

import android.content.Context
import android.support.constraint.ConstraintLayout
import android.util.AttributeSet
import android.view.View
import io.horizontalsystems.bankwallet.R
import io.horizontalsystems.bankwallet.core.App
import io.horizontalsystems.bankwallet.modules.transactions.TransactionStatus
import io.horizontalsystems.bankwallet.viewHelpers.LayoutHelper
import kotlinx.android.synthetic.main.view_transaction_info_status.view.*

open class TransactionInfoStatusView : ConstraintLayout {

    constructor(context: Context) : super(context) {
        initializeViews()
    }

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
        initializeViews()
    }

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
        initializeViews()
    }

    private fun initializeViews() {
        ConstraintLayout.inflate(context, R.layout.view_transaction_info_status, this)
    }

    fun bind(transactionStatus: TransactionStatus) {
        progressBarWrapper.visibility = View.GONE
        statusIcon.visibility = View.GONE
        statusPendingText.visibility = View.GONE
        when (transactionStatus) {
            is TransactionStatus.Completed -> {
                statusIcon.setImageDrawable(LayoutHelper.d(R.drawable.checkmark_green, App.instance))
                statusIcon.visibility = View.VISIBLE
            }
            is TransactionStatus.Processing -> {
                progressBarWrapper.visibility = View.VISIBLE
                fillProgress(transactionStatus)
            }
            else -> {
                statusIcon.setImageDrawable(LayoutHelper.d(R.drawable.pending, App.instance))
                statusIcon.visibility = View.VISIBLE
                statusPendingText.visibility = View.VISIBLE
            }
        }
        invalidate()
    }

    private fun fillProgress(transactionStatus: TransactionStatus.Processing) {
        val greyBar = R.drawable.status_progress_bar_grey
        val greenBar = R.drawable.status_progress_bar_green

        progressBar1.setImageResource(if (transactionStatus.progress >= 1) greenBar else greyBar)
        progressBar2.setImageResource(if (transactionStatus.progress >= 2) greenBar else greyBar)
        progressBar3.setImageResource(if (transactionStatus.progress >= 3) greenBar else greyBar)
        progressBar4.setImageResource(if (transactionStatus.progress >= 4) greenBar else greyBar)
        progressBar5.setImageResource(if (transactionStatus.progress >= 5) greenBar else greyBar)
        progressBar6.setImageResource(if (transactionStatus.progress >= 6) greenBar else greyBar)
    }

}

package io.horizontalsystems.bankwallet.modules.transactions.transactionInfo

import android.arch.lifecycle.ViewModel
import io.horizontalsystems.bankwallet.SingleLiveEvent
import io.horizontalsystems.bankwallet.modules.transactions.CoinCode
import io.horizontalsystems.bankwallet.modules.transactions.TransactionViewItem

class TransactionInfoViewModel : ViewModel(), TransactionInfoModule.View, TransactionInfoModule.Router {

    lateinit var delegate: TransactionInfoModule.ViewDelegate

    val transactionLiveData = SingleLiveEvent<TransactionViewItem>()
    val showFullInfoLiveEvent = SingleLiveEvent<Pair<String, String>>()
    val showCopiedLiveEvent = SingleLiveEvent<Unit>()

    fun init() {
        TransactionInfoModule.init(this, this)
    }

    override fun showCopied() {
        showCopiedLiveEvent.call()
    }

    override fun openFullInfo(transactionHash: String, coinCode: CoinCode) {
        showFullInfoLiveEvent.value = Pair(transactionHash, coinCode)
    }

    fun setViewItem(transactionViewItem: TransactionViewItem) {
        transactionLiveData.value = transactionViewItem
    }

    fun onClickTransactionId() {
        transactionLiveData.value?.let {
            delegate.onCopy(it.transactionHash)
        }
    }

    fun onClickOpenFillInfo() {
        transactionLiveData.value?.let {
            delegate.openFullInfo(it.transactionHash, it.coinValue.coinCode)
        }
    }

    fun onClickFrom() {
        transactionLiveData.value?.from?.let {
            delegate.onCopy(it)
        }
    }

    fun onClickTo() {
        transactionLiveData.value?.to?.let {
            delegate.onCopy(it)
        }
    }
}