Merge branch 'feat_equiv_values' into develop
This commit is contained in:
commit
577e3ae01f
20 changed files with 519 additions and 34 deletions
|
@ -118,8 +118,9 @@ dependencies {
|
||||||
// Core library
|
// Core library
|
||||||
androidTestImplementation 'androidx.test:core:1.1.0'
|
androidTestImplementation 'androidx.test:core:1.1.0'
|
||||||
|
|
||||||
// testImplementation "androidx.arch.core:core-testing:$lifecycle_version"
|
androidTestImplementation "androidx.arch.core:core-testing:$lifecycle_version"
|
||||||
androidTestImplementation "androidx.room:room-testing:$room_version"
|
androidTestImplementation "androidx.room:room-testing:$room_version"
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
|
||||||
|
androidTestImplementation 'com.jraska.livedata:testing-ktx:1.0.0'
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
package cy.agorise.bitsybitshareswallet
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
|
||||||
|
import java.util.concurrent.CountDownLatch
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
object LiveDataTestUtil {
|
||||||
|
fun <T> getValue(liveData: LiveData<T>): T {
|
||||||
|
val data = arrayOfNulls<Any>(1)
|
||||||
|
val latch = CountDownLatch(1)
|
||||||
|
val observer = object : Observer<T> {
|
||||||
|
override fun onChanged(o: T?) {
|
||||||
|
data[0] = o
|
||||||
|
latch.countDown()
|
||||||
|
liveData.removeObserver(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
liveData.observeForever(observer)
|
||||||
|
latch.await(2, TimeUnit.SECONDS)
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
return data[0] as T
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,20 +1,19 @@
|
||||||
package cy.agorise.bitsybitshareswallet;
|
package cy.agorise.bitsybitshareswallet;
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
import androidx.test.core.app.ApplicationProvider
|
import androidx.test.core.app.ApplicationProvider
|
||||||
import androidx.test.runner.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import cy.agorise.bitsybitshareswallet.database.BitsyDatabase
|
import cy.agorise.bitsybitshareswallet.database.BitsyDatabase
|
||||||
import cy.agorise.bitsybitshareswallet.database.entities.Merchant
|
import cy.agorise.bitsybitshareswallet.database.entities.Merchant
|
||||||
import org.junit.After
|
import org.junit.*
|
||||||
import org.junit.Assert
|
|
||||||
import org.junit.Before
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
public class MerchantQueryTest {
|
class MerchantQueryTest {
|
||||||
|
@get:Rule val testRule = InstantTaskExecutorRule()
|
||||||
private lateinit var db: BitsyDatabase
|
private lateinit var db: BitsyDatabase
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
package cy.agorise.bitsybitshareswallet
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||||
|
import androidx.room.Room
|
||||||
|
import androidx.test.core.app.ApplicationProvider
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import com.jraska.livedata.test
|
||||||
|
import cy.agorise.bitsybitshareswallet.database.BitsyDatabase
|
||||||
|
import cy.agorise.bitsybitshareswallet.database.entities.EquivalentValue
|
||||||
|
import cy.agorise.bitsybitshareswallet.database.entities.Transfer
|
||||||
|
import org.junit.*
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class TransfersTests {
|
||||||
|
val TAG = "TransfersTests"
|
||||||
|
@get:Rule val testRule = InstantTaskExecutorRule()
|
||||||
|
private lateinit var db: BitsyDatabase
|
||||||
|
private val context = ApplicationProvider.getApplicationContext<Context>()
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun createDb() {
|
||||||
|
Log.d(TAG,"createDb")
|
||||||
|
db = Room.inMemoryDatabaseBuilder(context, BitsyDatabase::class.java).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun closeDb(){
|
||||||
|
Log.d(TAG,"closeDB")
|
||||||
|
db.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares the database to the testGetTransfersMissingEquivalentValues and testGetTransfersMissingEquivalentValues2
|
||||||
|
* tests.
|
||||||
|
*/
|
||||||
|
private fun prepareMissingEquivalentValues(){
|
||||||
|
// We create 2 transfers for the 'transfers' table, but only one of them will have an equivalent value entry
|
||||||
|
val t1 = Transfer("1.11.702181910", 34118155, 1485018549, 264174, "1.3.0", "1.2.32567","1.2.139293",15869682,"1.3.0","")
|
||||||
|
val t2 = Transfer("1.11.684483739", 33890367, 1547171166, 11030, "1.3.0", "1.2.139293","1.2.1029856",98,"1.3.120","")
|
||||||
|
db.transferDao().insert(t1)
|
||||||
|
db.transferDao().insert(t2)
|
||||||
|
|
||||||
|
// Here's the equivalent value for the first transaction inserted (t1)
|
||||||
|
val equivalentValue = EquivalentValue("1.11.702181910", 0, "usd")
|
||||||
|
db.equivalentValueDao().insert(equivalentValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test makes use of the LiveData Testing library and its objective is to prove that
|
||||||
|
* the TransferDao#getTransfersWithMissingValueIn(symbol: String) will return only the
|
||||||
|
* second 'transfer' entry.
|
||||||
|
* <p>
|
||||||
|
* @see cy.agorise.bitsybitshareswallet.database.daos.TransferDao.getTransfersWithMissingValueIn
|
||||||
|
* @see cy.agorise.bitsybitshareswallet.LiveDataTestUtil
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun testGetTransfersMissingEquivalentValues(){
|
||||||
|
prepareMissingEquivalentValues()
|
||||||
|
db.transferDao()
|
||||||
|
.getTransfersWithMissingValueIn("usd")
|
||||||
|
.test()
|
||||||
|
.awaitValue()
|
||||||
|
.assertHasValue()
|
||||||
|
.assertValue { transfers -> transfers.size == 1 }
|
||||||
|
.assertValue { transfers -> transfers[0].id == "1.11.684483739"}
|
||||||
|
.assertValue { transfers -> transfers[0].blockNumber == 33890367L}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test makes use of the simple LiveDataTestUtil class and its objective is to prove that
|
||||||
|
* the TransferDao#getTransfersWithMissingValueIn(symbol: String) will return only the
|
||||||
|
* second 'transfer' entry.
|
||||||
|
* <p>
|
||||||
|
* @see cy.agorise.bitsybitshareswallet.LiveDataTestUtil
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun testGetTransfersMissingEquivalentValues2(){
|
||||||
|
prepareMissingEquivalentValues()
|
||||||
|
val transfers: List<Transfer> = LiveDataTestUtil.getValue(db.transferDao().getTransfersWithMissingValueIn("usd"))
|
||||||
|
Assert.assertNotNull(transfers)
|
||||||
|
Assert.assertEquals(1, transfers.size)
|
||||||
|
Assert.assertEquals("1.11.684483739", transfers[0].id)
|
||||||
|
Assert.assertEquals(33890367, transfers[0].blockNumber)
|
||||||
|
Log.d(TAG, "transfer ${transfers[0]}");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testGetTransfersWithMissingBtsValue(){
|
||||||
|
val t1 = Transfer("1.11.702181910",
|
||||||
|
34118155,
|
||||||
|
1485018549,
|
||||||
|
264174,
|
||||||
|
"1.3.0",
|
||||||
|
"1.2.32567",
|
||||||
|
"1.2.139293",
|
||||||
|
15869682,
|
||||||
|
"1.3.0","")
|
||||||
|
val t2 = Transfer("1.11.684483739",
|
||||||
|
33890367,
|
||||||
|
1547171166,
|
||||||
|
11030,
|
||||||
|
"1.3.0",
|
||||||
|
"1.2.139293",
|
||||||
|
"1.2.1029856",
|
||||||
|
98,
|
||||||
|
"1.3.120",
|
||||||
|
"",
|
||||||
|
1000)
|
||||||
|
db.transferDao().insert(t1)
|
||||||
|
db.transferDao().insert(t2)
|
||||||
|
db.transferDao().getTransfersWithMissingBtsValue()
|
||||||
|
.test()
|
||||||
|
.assertHasValue()
|
||||||
|
.assertValue { transfer -> transfer.id == "1.11.702181910" }
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
|
import android.os.AsyncTask
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
|
@ -17,29 +18,32 @@ import com.crashlytics.android.Crashlytics
|
||||||
import com.crashlytics.android.core.CrashlyticsCore
|
import com.crashlytics.android.core.CrashlyticsCore
|
||||||
import cy.agorise.bitsybitshareswallet.BuildConfig
|
import cy.agorise.bitsybitshareswallet.BuildConfig
|
||||||
import cy.agorise.bitsybitshareswallet.database.entities.Balance
|
import cy.agorise.bitsybitshareswallet.database.entities.Balance
|
||||||
|
import cy.agorise.bitsybitshareswallet.database.entities.Transfer
|
||||||
import cy.agorise.bitsybitshareswallet.processors.TransfersLoader
|
import cy.agorise.bitsybitshareswallet.processors.TransfersLoader
|
||||||
import cy.agorise.bitsybitshareswallet.repositories.AssetRepository
|
import cy.agorise.bitsybitshareswallet.repositories.AssetRepository
|
||||||
import cy.agorise.bitsybitshareswallet.utils.Constants
|
import cy.agorise.bitsybitshareswallet.utils.Constants
|
||||||
import cy.agorise.bitsybitshareswallet.viewmodels.BalanceViewModel
|
import cy.agorise.bitsybitshareswallet.viewmodels.BalanceViewModel
|
||||||
|
import cy.agorise.bitsybitshareswallet.viewmodels.ConnectedActivityViewModel
|
||||||
import cy.agorise.bitsybitshareswallet.viewmodels.TransferViewModel
|
import cy.agorise.bitsybitshareswallet.viewmodels.TransferViewModel
|
||||||
import cy.agorise.bitsybitshareswallet.viewmodels.UserAccountViewModel
|
import cy.agorise.bitsybitshareswallet.viewmodels.UserAccountViewModel
|
||||||
import cy.agorise.graphenej.Asset
|
import cy.agorise.graphenej.Asset
|
||||||
import cy.agorise.graphenej.AssetAmount
|
import cy.agorise.graphenej.AssetAmount
|
||||||
import cy.agorise.graphenej.UserAccount
|
import cy.agorise.graphenej.UserAccount
|
||||||
|
import cy.agorise.graphenej.api.ApiAccess
|
||||||
import cy.agorise.graphenej.api.ConnectionStatusUpdate
|
import cy.agorise.graphenej.api.ConnectionStatusUpdate
|
||||||
import cy.agorise.graphenej.api.android.NetworkService
|
import cy.agorise.graphenej.api.android.NetworkService
|
||||||
import cy.agorise.graphenej.api.android.RxBus
|
import cy.agorise.graphenej.api.android.RxBus
|
||||||
import cy.agorise.graphenej.api.calls.*
|
import cy.agorise.graphenej.api.calls.*
|
||||||
import cy.agorise.graphenej.models.AccountProperties
|
import cy.agorise.graphenej.models.*
|
||||||
import cy.agorise.graphenej.models.BlockHeader
|
|
||||||
import cy.agorise.graphenej.models.FullAccountDetails
|
|
||||||
import cy.agorise.graphenej.models.JsonRpcResponse
|
|
||||||
import io.fabric.sdk.android.Fabric
|
import io.fabric.sdk.android.Fabric
|
||||||
|
import io.reactivex.Observable
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.CompositeDisposable
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
import java.text.ParseException
|
import java.text.ParseException
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
import kotlin.collections.HashMap
|
import kotlin.collections.HashMap
|
||||||
|
|
||||||
|
@ -58,11 +62,13 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
||||||
private const val RESPONSE_GET_ACCOUNT_BALANCES = 3
|
private const val RESPONSE_GET_ACCOUNT_BALANCES = 3
|
||||||
private const val RESPONSE_GET_ASSETS = 4
|
private const val RESPONSE_GET_ASSETS = 4
|
||||||
private const val RESPONSE_GET_BLOCK_HEADER = 5
|
private const val RESPONSE_GET_BLOCK_HEADER = 5
|
||||||
|
private const val RESPONSE_GET_MARKET_HISTORY = 6
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var mUserAccountViewModel: UserAccountViewModel
|
private lateinit var mUserAccountViewModel: UserAccountViewModel
|
||||||
private lateinit var mBalanceViewModel: BalanceViewModel
|
private lateinit var mBalanceViewModel: BalanceViewModel
|
||||||
private lateinit var mTransferViewModel: TransferViewModel
|
private lateinit var mTransferViewModel: TransferViewModel
|
||||||
|
private lateinit var mConnectedActivityViewModel: ConnectedActivityViewModel
|
||||||
|
|
||||||
private lateinit var mAssetRepository: AssetRepository
|
private lateinit var mAssetRepository: AssetRepository
|
||||||
|
|
||||||
|
@ -71,8 +77,8 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
||||||
|
|
||||||
private val mHandler = Handler()
|
private val mHandler = Handler()
|
||||||
|
|
||||||
// Disposable returned at the bus subscription
|
// Composite disposable used to clear all disposables once the activity is destroyed
|
||||||
private var mDisposable: Disposable? = null
|
private val mCompositeDisposable = CompositeDisposable()
|
||||||
|
|
||||||
private var storedOpCount: Long = -1
|
private var storedOpCount: Long = -1
|
||||||
|
|
||||||
|
@ -90,6 +96,10 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
||||||
|
|
||||||
private var blockNumberWithMissingTime = 0L
|
private var blockNumberWithMissingTime = 0L
|
||||||
|
|
||||||
|
// Variable used to hold a reference to the specific Transfer instance which we're currently trying
|
||||||
|
// to resolve an equivalent BTS value
|
||||||
|
var transfer: Transfer? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flag used to keep track of the NetworkService binding state
|
* Flag used to keep track of the NetworkService binding state
|
||||||
*/
|
*/
|
||||||
|
@ -107,6 +117,11 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
||||||
|
|
||||||
mAssetRepository = AssetRepository(this)
|
mAssetRepository = AssetRepository(this)
|
||||||
|
|
||||||
|
// Configure ConnectedActivityViewModel to obtain missing equivalent values
|
||||||
|
mConnectedActivityViewModel = ViewModelProviders.of(this).get(ConnectedActivityViewModel::class.java)
|
||||||
|
|
||||||
|
mConnectedActivityViewModel.observeMissingEquivalentValuesIn("usd") //TODO: Obtain this from shared preferences?
|
||||||
|
|
||||||
// Configure UserAccountViewModel to obtain the missing account ids
|
// Configure UserAccountViewModel to obtain the missing account ids
|
||||||
mUserAccountViewModel = ViewModelProviders.of(this).get(UserAccountViewModel::class.java)
|
mUserAccountViewModel = ViewModelProviders.of(this).get(UserAccountViewModel::class.java)
|
||||||
|
|
||||||
|
@ -139,12 +154,11 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
||||||
mTransferViewModel.getTransferBlockNumberWithMissingTime().observe(this, Observer<Long>{ blockNumber ->
|
mTransferViewModel.getTransferBlockNumberWithMissingTime().observe(this, Observer<Long>{ blockNumber ->
|
||||||
if (blockNumber != null && blockNumber != blockNumberWithMissingTime) {
|
if (blockNumber != null && blockNumber != blockNumberWithMissingTime) {
|
||||||
blockNumberWithMissingTime = blockNumber
|
blockNumberWithMissingTime = blockNumber
|
||||||
Log.d(TAG, "Block number: $blockNumber, Time: ${System.currentTimeMillis()}")
|
|
||||||
mHandler.post(mRequestBlockMissingTimeTask)
|
mHandler.post(mRequestBlockMissingTimeTask)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
mDisposable = RxBus.getBusInstance()
|
val disposable = RxBus.getBusInstance()
|
||||||
.asFlowable()
|
.asFlowable()
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe({
|
.subscribe({
|
||||||
|
@ -152,6 +166,7 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
||||||
}, {
|
}, {
|
||||||
this.handleError(it)
|
this.handleError(it)
|
||||||
})
|
})
|
||||||
|
mCompositeDisposable.add(disposable)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -202,6 +217,7 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
||||||
handleBlockHeader(message.result as BlockHeader, blockNumber)
|
handleBlockHeader(message.result as BlockHeader, blockNumber)
|
||||||
requestIdToBlockNumberMap.remove(message.id)
|
requestIdToBlockNumberMap.remove(message.id)
|
||||||
}
|
}
|
||||||
|
RESPONSE_GET_MARKET_HISTORY -> handleMarketData(message.result as List<BucketObject>)
|
||||||
}
|
}
|
||||||
responseMap.remove(message.id)
|
responseMap.remove(message.id)
|
||||||
}
|
}
|
||||||
|
@ -223,10 +239,35 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
||||||
// If we got a disconnection notification, we should clear our response map, since
|
// If we got a disconnection notification, we should clear our response map, since
|
||||||
// all its stored request ids will now be reset
|
// all its stored request ids will now be reset
|
||||||
responseMap.clear()
|
responseMap.clear()
|
||||||
|
} else if (message.updateCode == ConnectionStatusUpdate.API_UPDATE) {
|
||||||
|
// If we got an API update
|
||||||
|
if(message.api == ApiAccess.API_HISTORY) {
|
||||||
|
// Starts the procedure that will obtain the missing equivalent values
|
||||||
|
mTransferViewModel
|
||||||
|
.getTransfersWithMissingBtsValue().observe(this, Observer<Transfer> {
|
||||||
|
if(it != null) handleTransfersWithMissingBtsValue(it)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called whenever we get a list of transfers with their bts value missing.
|
||||||
|
*/
|
||||||
|
private fun handleTransfersWithMissingBtsValue(transfer: Transfer) {
|
||||||
|
if(mNetworkService?.isConnected == true){
|
||||||
|
val base = Asset(transfer.transferAssetId)
|
||||||
|
val quote = Asset("1.3.0")
|
||||||
|
val bucket: Long = TimeUnit.SECONDS.convert(1, TimeUnit.DAYS)
|
||||||
|
val end: Long = transfer.timestamp * 1000L
|
||||||
|
val start: Long = (transfer.timestamp - bucket) * 1000L
|
||||||
|
val id = mNetworkService!!.sendMessage(GetMarketHistory(base, quote, bucket, start, end), GetMarketHistory.REQUIRED_API)
|
||||||
|
responseMap[id] = RESPONSE_GET_MARKET_HISTORY
|
||||||
|
this.transfer = transfer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method called whenever a response to the 'get_full_accounts' API call has been detected.
|
* Method called whenever a response to the 'get_full_accounts' API call has been detected.
|
||||||
* @param accountDetails De-serialized account details object
|
* @param accountDetails De-serialized account details object
|
||||||
|
@ -275,7 +316,6 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleBalanceUpdate(assetAmountList: List<AssetAmount>) {
|
private fun handleBalanceUpdate(assetAmountList: List<AssetAmount>) {
|
||||||
Log.d(TAG, "handleBalanceUpdate")
|
|
||||||
val now = System.currentTimeMillis() / 1000
|
val now = System.currentTimeMillis() / 1000
|
||||||
val balances = ArrayList<Balance>()
|
val balances = ArrayList<Balance>()
|
||||||
for (assetAmount in assetAmountList) {
|
for (assetAmount in assetAmountList) {
|
||||||
|
@ -330,6 +370,25 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleMarketData(buckets: List<BucketObject>) {
|
||||||
|
if(buckets.isNotEmpty()){
|
||||||
|
Log.d(TAG,"handleMarketData. Bucket is not empty")
|
||||||
|
val bucket = buckets[0]
|
||||||
|
val pair = Pair(transfer, bucket)
|
||||||
|
val disposable = Observable.just(pair)
|
||||||
|
.subscribeOn(Schedulers.computation())
|
||||||
|
.map { mTransferViewModel.updateBtsValue(it.first!!, it.second) }
|
||||||
|
.subscribe({},{
|
||||||
|
Log.e(TAG,"Error at updateBtsValue. Msg: ${it.message}")
|
||||||
|
for(line in it.stackTrace) Log.e(TAG, "${line.className}#${line.methodName}:${line.lineNumber}")
|
||||||
|
})
|
||||||
|
mCompositeDisposable.add(disposable)
|
||||||
|
}else{
|
||||||
|
Log.i(TAG,"handleMarketData. Bucket IS empty")
|
||||||
|
AsyncTask.execute { mTransferViewModel.updateBtsValue(transfer!!, Transfer.ERROR) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateBalances() {
|
private fun updateBalances() {
|
||||||
if (mNetworkService?.isConnected == true) {
|
if (mNetworkService?.isConnected == true) {
|
||||||
val id = mNetworkService!!.sendMessage(GetAccountBalances(mCurrentAccount, ArrayList()),
|
val id = mNetworkService!!.sendMessage(GetAccountBalances(mCurrentAccount, ArrayList()),
|
||||||
|
@ -444,6 +503,6 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
if (!mDisposable!!.isDisposed) mDisposable!!.dispose()
|
if(!mCompositeDisposable.isDisposed) mCompositeDisposable.dispose()
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -17,4 +17,7 @@ interface AssetDao {
|
||||||
|
|
||||||
@Query("SELECT id, symbol, precision, description, issuer FROM assets INNER JOIN balances WHERE assets.id = balances.asset_id AND balances.asset_amount > 0")
|
@Query("SELECT id, symbol, precision, description, issuer FROM assets INNER JOIN balances WHERE assets.id = balances.asset_id AND balances.asset_amount > 0")
|
||||||
fun getAllNonZero(): LiveData<List<Asset>>
|
fun getAllNonZero(): LiveData<List<Asset>>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM assets WHERE id = :assetId")
|
||||||
|
fun getAssetDetails(assetId: String): Asset
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
package cy.agorise.bitsybitshareswallet.database.daos
|
package cy.agorise.bitsybitshareswallet.database.daos
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.room.Dao
|
import androidx.room.*
|
||||||
import androidx.room.Insert
|
|
||||||
import androidx.room.OnConflictStrategy
|
|
||||||
import androidx.room.Query
|
|
||||||
import cy.agorise.bitsybitshareswallet.database.entities.Transfer
|
import cy.agorise.bitsybitshareswallet.database.entities.Transfer
|
||||||
|
import io.reactivex.Observable
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
interface TransferDao {
|
interface TransferDao {
|
||||||
@Insert
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
fun insert(transfer: Transfer)
|
fun insert(transfer: Transfer)
|
||||||
|
|
||||||
|
@Update()
|
||||||
|
fun update(transfer: Transfer)
|
||||||
|
|
||||||
// TODO find a way to return number of added rows
|
// TODO find a way to return number of added rows
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
fun insertAll(transfers: List<Transfer>)
|
fun insertAll(transfers: List<Transfer>)
|
||||||
|
@ -29,6 +30,12 @@ interface TransferDao {
|
||||||
@Query("SELECT block_number FROM transfers WHERE timestamp='0' LIMIT 1")
|
@Query("SELECT block_number FROM transfers WHERE timestamp='0' LIMIT 1")
|
||||||
fun getTransferBlockNumberWithMissingTime(): LiveData<Long>
|
fun getTransferBlockNumberWithMissingTime(): LiveData<Long>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM transfers WHERE timestamp != 0 AND bts_value = -1 AND transfer_asset_id != '1.3.0' LIMIT 1")
|
||||||
|
fun getTransfersWithMissingBtsValue(): LiveData<Transfer>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM transfers WHERE id NOT IN (SELECT transfer_id FROM equivalent_values WHERE symbol = :symbol) AND bts_value >= 0 LIMIT 1")
|
||||||
|
fun getTransfersWithMissingValueIn(symbol: String): Observable<Transfer>
|
||||||
|
|
||||||
@Query("DELETE FROM transfers")
|
@Query("DELETE FROM transfers")
|
||||||
fun deleteAll()
|
fun deleteAll()
|
||||||
}
|
}
|
|
@ -17,5 +17,18 @@ data class Transfer (
|
||||||
@ColumnInfo(name = "transfer_amount") val transferAmount: Long,
|
@ColumnInfo(name = "transfer_amount") val transferAmount: Long,
|
||||||
@ColumnInfo(name = "transfer_asset_id") val transferAssetId: String, // TODO should be foreign key to Asset
|
@ColumnInfo(name = "transfer_asset_id") val transferAssetId: String, // TODO should be foreign key to Asset
|
||||||
@ColumnInfo(name = "memo") val memo: String,
|
@ColumnInfo(name = "memo") val memo: String,
|
||||||
@ColumnInfo(name = "bts_value") val btsValue: Long? = -1
|
@ColumnInfo(name = "bts_value") var btsValue: Long? = Transfer.NOT_CALCULATED
|
||||||
)
|
){
|
||||||
|
companion object {
|
||||||
|
// Constant used to specify an uninitialized BTS equivalent value
|
||||||
|
val NOT_CALCULATED: Long? = -1L
|
||||||
|
// Constant used to specify a BTS equivalent value whose calculation returned an error
|
||||||
|
val ERROR: Long? = -2L
|
||||||
|
}
|
||||||
|
init {
|
||||||
|
if(transferAssetId.equals("1.3.0")){
|
||||||
|
// If the transferred asset is BTS, we can fill the btsValue field immediately
|
||||||
|
btsValue = transferAmount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package cy.agorise.bitsybitshareswallet.models.coingecko
|
||||||
|
|
||||||
|
data class HistoricalPrice(val id: String,
|
||||||
|
val symbol: String,
|
||||||
|
val market_data: MarketData)
|
|
@ -0,0 +1,3 @@
|
||||||
|
package cy.agorise.bitsybitshareswallet.models.coingecko
|
||||||
|
|
||||||
|
data class MarketData(var current_price: HashMap<String, Double>)
|
|
@ -0,0 +1,22 @@
|
||||||
|
package cy.agorise.bitsybitshareswallet.models.coingecko
|
||||||
|
|
||||||
|
import com.google.gson.JsonDeserializationContext
|
||||||
|
import com.google.gson.JsonDeserializer
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import junit.framework.Assert
|
||||||
|
import java.lang.reflect.Type
|
||||||
|
|
||||||
|
class MarketDataDeserializer : JsonDeserializer<MarketData> {
|
||||||
|
override fun deserialize(json: JsonElement?, typeOfT: Type?, context: JsonDeserializationContext?): MarketData {
|
||||||
|
val hashMap = HashMap<String, Double>()
|
||||||
|
val obj = json?.asJsonObject?.get("current_price")?.asJsonObject
|
||||||
|
if(obj != null){
|
||||||
|
val keySet = obj.asJsonObject.keySet()
|
||||||
|
for(key in keySet){
|
||||||
|
println("$key -> : ${obj[key].asDouble}")
|
||||||
|
hashMap[key] = obj[key].asDouble
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return MarketData(hashMap)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package cy.agorise.bitsybitshareswallet.network
|
||||||
|
|
||||||
|
import retrofit2.Call
|
||||||
|
import cy.agorise.bitsybitshareswallet.models.coingecko.HistoricalPrice
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Headers
|
||||||
|
import retrofit2.http.Query
|
||||||
|
|
||||||
|
interface CoingeckoService {
|
||||||
|
|
||||||
|
@Headers("Content-Type: application/json")
|
||||||
|
@GET("/api/v3/coins/bitshares/history")
|
||||||
|
fun getHistoricalValueSync(@Query("id") id: String,
|
||||||
|
@Query("date") date: String,
|
||||||
|
@Query("localization") localization: Boolean): Call<HistoricalPrice>
|
||||||
|
}
|
|
@ -3,6 +3,8 @@ package cy.agorise.bitsybitshareswallet.network;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
|
import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
|
||||||
|
import cy.agorise.bitsybitshareswallet.models.coingecko.MarketData;
|
||||||
|
import cy.agorise.bitsybitshareswallet.models.coingecko.MarketDataDeserializer;
|
||||||
import okhttp3.Interceptor;
|
import okhttp3.Interceptor;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
import okhttp3.logging.HttpLoggingInterceptor;
|
import okhttp3.logging.HttpLoggingInterceptor;
|
||||||
|
@ -22,23 +24,26 @@ public class ServiceGenerator{
|
||||||
|
|
||||||
private static HashMap<Class<?>, Object> Services;
|
private static HashMap<Class<?>, Object> Services;
|
||||||
|
|
||||||
public ServiceGenerator(String apiBaseUrl) {
|
public ServiceGenerator(String apiBaseUrl, Gson gson) {
|
||||||
API_BASE_URL= apiBaseUrl;
|
API_BASE_URL= apiBaseUrl;
|
||||||
logging = new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY);
|
logging = new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY);
|
||||||
httpClient = new OkHttpClient.Builder().addInterceptor(logging);
|
httpClient = new OkHttpClient.Builder().addInterceptor(logging);
|
||||||
builder = new Retrofit.Builder()
|
builder = new Retrofit.Builder()
|
||||||
.baseUrl(API_BASE_URL)
|
.baseUrl(API_BASE_URL)
|
||||||
.addConverterFactory(GsonConverterFactory.create(getGson()))
|
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||||
.addCallAdapterFactory(RxJava2CallAdapterFactory.create());
|
.addCallAdapterFactory(RxJava2CallAdapterFactory.create());
|
||||||
Services = new HashMap<Class<?>, Object>();
|
Services = new HashMap<Class<?>, Object>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ServiceGenerator(String apiBaseUrl){
|
||||||
|
this(apiBaseUrl, new Gson());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Customizes the Gson instance with specific de-serialization logic
|
* Customizes the Gson instance with specific de-serialization logic
|
||||||
*/
|
*/
|
||||||
private Gson getGson(){
|
private Gson getGson(){
|
||||||
GsonBuilder builder = new GsonBuilder();
|
GsonBuilder builder = new GsonBuilder();
|
||||||
|
|
||||||
return builder.create();
|
return builder.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,6 +81,11 @@ public class ServiceGenerator{
|
||||||
httpClient.readTimeout(5, TimeUnit.MINUTES);
|
httpClient.readTimeout(5, TimeUnit.MINUTES);
|
||||||
httpClient.connectTimeout(5, TimeUnit.MINUTES);
|
httpClient.connectTimeout(5, TimeUnit.MINUTES);
|
||||||
OkHttpClient client = httpClient.build();
|
OkHttpClient client = httpClient.build();
|
||||||
|
if(serviceClass == CoingeckoService.class){
|
||||||
|
// The MarketData class needs a custom de-serializer
|
||||||
|
Gson gson = new GsonBuilder().registerTypeAdapter(MarketData.class, new MarketDataDeserializer()).create();
|
||||||
|
builder.addConverterFactory(GsonConverterFactory.create(gson));
|
||||||
|
}
|
||||||
Retrofit retrofit = builder.client(client).build();
|
Retrofit retrofit = builder.client(client).build();
|
||||||
return retrofit.create(serviceClass);
|
return retrofit.create(serviceClass);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@ package cy.agorise.bitsybitshareswallet.repositories
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.AsyncTask
|
import android.os.AsyncTask
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import cy.agorise.bitsybitshareswallet.database.daos.AssetDao
|
|
||||||
import cy.agorise.bitsybitshareswallet.database.BitsyDatabase
|
import cy.agorise.bitsybitshareswallet.database.BitsyDatabase
|
||||||
|
import cy.agorise.bitsybitshareswallet.database.daos.AssetDao
|
||||||
import cy.agorise.bitsybitshareswallet.database.entities.Asset
|
import cy.agorise.bitsybitshareswallet.database.entities.Asset
|
||||||
|
|
||||||
class AssetRepository internal constructor(context: Context) {
|
class AssetRepository internal constructor(context: Context) {
|
||||||
|
@ -24,6 +24,10 @@ class AssetRepository internal constructor(context: Context) {
|
||||||
insertAllAsyncTask(mAssetDao).execute(assets)
|
insertAllAsyncTask(mAssetDao).execute(assets)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getAssetDetails(assetId: String): Asset {
|
||||||
|
return mAssetDao.getAssetDetails(assetId)
|
||||||
|
}
|
||||||
|
|
||||||
private class insertAllAsyncTask internal constructor(private val mAsyncTaskDao: AssetDao) :
|
private class insertAllAsyncTask internal constructor(private val mAsyncTaskDao: AssetDao) :
|
||||||
AsyncTask<List<Asset>, Void, Void>() {
|
AsyncTask<List<Asset>, Void, Void>() {
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package cy.agorise.bitsybitshareswallet.repositories
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import cy.agorise.bitsybitshareswallet.database.BitsyDatabase
|
||||||
|
import cy.agorise.bitsybitshareswallet.database.daos.EquivalentValueDao
|
||||||
|
import cy.agorise.bitsybitshareswallet.database.daos.TransferDao
|
||||||
|
|
||||||
|
class EquivalentValuesRepository(context: Context) {
|
||||||
|
|
||||||
|
private val mEquivalentValuesDao: EquivalentValueDao?
|
||||||
|
private val mTransfersDao: TransferDao?
|
||||||
|
|
||||||
|
init {
|
||||||
|
val db = BitsyDatabase.getDatabase(context)
|
||||||
|
mEquivalentValuesDao = db?.equivalentValueDao()
|
||||||
|
mTransfersDao = db?.transferDao()
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,25 +2,42 @@ package cy.agorise.bitsybitshareswallet.repositories
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.AsyncTask
|
import android.os.AsyncTask
|
||||||
|
import android.util.Log
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import cy.agorise.bitsybitshareswallet.database.BitsyDatabase
|
import cy.agorise.bitsybitshareswallet.database.BitsyDatabase
|
||||||
|
import cy.agorise.bitsybitshareswallet.database.daos.EquivalentValueDao
|
||||||
import cy.agorise.bitsybitshareswallet.database.daos.TransferDao
|
import cy.agorise.bitsybitshareswallet.database.daos.TransferDao
|
||||||
|
import cy.agorise.bitsybitshareswallet.database.entities.EquivalentValue
|
||||||
import cy.agorise.bitsybitshareswallet.database.entities.Transfer
|
import cy.agorise.bitsybitshareswallet.database.entities.Transfer
|
||||||
|
import cy.agorise.bitsybitshareswallet.network.CoingeckoService
|
||||||
|
import cy.agorise.bitsybitshareswallet.network.ServiceGenerator
|
||||||
|
import cy.agorise.bitsybitshareswallet.utils.Constants
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
|
import io.reactivex.disposables.CompositeDisposable
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class TransferRepository internal constructor(context: Context) {
|
class TransferRepository internal constructor(context: Context) {
|
||||||
|
private val TAG = "TransferRepository"
|
||||||
private val mTransferDao: TransferDao
|
private val mTransferDao: TransferDao
|
||||||
|
private val mEquivalentValuesDao: EquivalentValueDao
|
||||||
|
private val compositeDisposable = CompositeDisposable()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val db = BitsyDatabase.getDatabase(context)
|
val db = BitsyDatabase.getDatabase(context)
|
||||||
mTransferDao = db!!.transferDao()
|
mTransferDao = db!!.transferDao()
|
||||||
|
mEquivalentValuesDao = db!!.equivalentValueDao()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun insertAll(transfers: List<Transfer>) {
|
fun insertAll(transfers: List<Transfer>) {
|
||||||
insertAllAsyncTask(mTransferDao).execute(transfers)
|
insertAllAsyncTask(mTransferDao).execute(transfers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun update(transfer: Transfer){
|
||||||
|
mTransferDao.insert(transfer)
|
||||||
|
}
|
||||||
|
|
||||||
fun setBlockTime(blockNumber: Long, timestamp: Long) {
|
fun setBlockTime(blockNumber: Long, timestamp: Long) {
|
||||||
setBlockTimeAsyncTask(mTransferDao).execute(Pair(blockNumber, timestamp))
|
setBlockTimeAsyncTask(mTransferDao).execute(Pair(blockNumber, timestamp))
|
||||||
}
|
}
|
||||||
|
@ -37,10 +54,71 @@ class TransferRepository internal constructor(context: Context) {
|
||||||
return mTransferDao.getTransferBlockNumberWithMissingTime()
|
return mTransferDao.getTransferBlockNumberWithMissingTime()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getTransfersWithMissingBtsValue(): LiveData<Transfer> {
|
||||||
|
return mTransferDao.getTransfersWithMissingBtsValue()
|
||||||
|
}
|
||||||
|
|
||||||
fun deleteAll() {
|
fun deleteAll() {
|
||||||
deleteAllAsyncTask(mTransferDao).execute()
|
deleteAllAsyncTask(mTransferDao).execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a subscription to the transfers table which will listen & process equivalent values.
|
||||||
|
*
|
||||||
|
* This function will create a subscription that will listen for missing equivalent values. This will
|
||||||
|
* automatically trigger a procedure designed to calculate the fiat equivalent value of any entry
|
||||||
|
* of the 'transactions' table that stil doesn't have a corresponding entry in the 'equivalent_values'
|
||||||
|
* table for that specific fiat currency.
|
||||||
|
*
|
||||||
|
* @param symbol The 3 letters symbol of the desired fiat currency.
|
||||||
|
*/
|
||||||
|
fun observeMissingEquivalentValuesIn(symbol: String) {
|
||||||
|
compositeDisposable.add(mTransferDao.getTransfersWithMissingValueIn(symbol)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(Schedulers.io())
|
||||||
|
.map { transfer -> obtainFiatValue(transfer, symbol) }
|
||||||
|
.subscribe({
|
||||||
|
Log.d(TAG,"Got equivalent value: $it")
|
||||||
|
mEquivalentValuesDao.insert(it)
|
||||||
|
},{
|
||||||
|
Log.e(TAG,"Error while trying to create a new equivalent value. Msg: ${it.message}")
|
||||||
|
for(element in it.stackTrace){
|
||||||
|
Log.e(TAG,"${element.className}#${element.methodName}:${element.lineNumber}")
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an equivalent value for a given transaction.
|
||||||
|
*
|
||||||
|
* Function used to perform a request to the Coingecko's price API trying to obtain the
|
||||||
|
* equivalent value of a specific [Transaction].
|
||||||
|
*
|
||||||
|
* @param transfer The transfer whose equivalent value we want to obtain
|
||||||
|
* @param symbol The symbol of the fiat that the equivalent value should be calculated in
|
||||||
|
* @return An instance of the [EquivalentValue] class, ready to be inserted into the database.
|
||||||
|
*/
|
||||||
|
fun obtainFiatValue(transfer: Transfer, symbol: String): EquivalentValue {
|
||||||
|
val sg = ServiceGenerator(Constants.COINGECKO_URL)
|
||||||
|
val dateFormat = SimpleDateFormat("dd-MM-yyyy", Locale.ROOT)
|
||||||
|
val date = Date(transfer.timestamp * 1000)
|
||||||
|
val response = sg.getService(CoingeckoService::class.java)
|
||||||
|
.getHistoricalValueSync("bitshares", dateFormat.format(date), false)
|
||||||
|
.execute()
|
||||||
|
var equivalentFiatValue = -1L
|
||||||
|
if(response.isSuccessful){
|
||||||
|
val price: Double = response.body()?.market_data?.current_price?.get(symbol) ?: -1.0
|
||||||
|
// The equivalent value is obtained by:
|
||||||
|
// 1- Dividing the base value by 100000 (BTS native precision)
|
||||||
|
// 2- Multiplying that BTS value by the unit price in the chosen fiat
|
||||||
|
// 3- Multiplying the resulting value by 100 in order to express it in cents
|
||||||
|
equivalentFiatValue = Math.round(transfer.btsValue?.div(1e5)?.times(price)?.times(100) ?: -1.0)
|
||||||
|
}else{
|
||||||
|
Log.w(TAG,"Request was not successful. code: ${response.code()}")
|
||||||
|
}
|
||||||
|
return EquivalentValue(transfer.id, equivalentFiatValue, symbol)
|
||||||
|
}
|
||||||
|
|
||||||
private class insertAllAsyncTask internal constructor(private val mAsyncTaskDao: TransferDao) :
|
private class insertAllAsyncTask internal constructor(private val mAsyncTaskDao: TransferDao) :
|
||||||
AsyncTask<List<Transfer>, Void, Void>() {
|
AsyncTask<List<Transfer>, Void, Void>() {
|
||||||
|
|
||||||
|
@ -67,4 +145,16 @@ class TransferRepository internal constructor(context: Context) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called whenever the disposables have to be cleared.
|
||||||
|
*
|
||||||
|
* Since this repository manages a subscription it is necessary to clear the disposable after we're done with it.
|
||||||
|
* The parent ViewModel will let us know when that subscription is no longer necessary and the resources can
|
||||||
|
* be cleared.
|
||||||
|
*/
|
||||||
|
fun onCleared() {
|
||||||
|
if(!compositeDisposable.isDisposed)
|
||||||
|
compositeDisposable.clear()
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -22,6 +22,9 @@ object Constants {
|
||||||
/** Faucet URL used to create new accounts */
|
/** Faucet URL used to create new accounts */
|
||||||
const val FAUCET_URL = "https://faucet.palmpay.io"
|
const val FAUCET_URL = "https://faucet.palmpay.io"
|
||||||
|
|
||||||
|
/** Coingecko's API URL */
|
||||||
|
const val COINGECKO_URL = "https://api.coingecko.com"
|
||||||
|
|
||||||
/** The user selected encrypted PIN */
|
/** The user selected encrypted PIN */
|
||||||
const val KEY_ENCRYPTED_PIN = "key_encrypted_pin"
|
const val KEY_ENCRYPTED_PIN = "key_encrypted_pin"
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package cy.agorise.bitsybitshareswallet.viewmodels
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import cy.agorise.bitsybitshareswallet.repositories.TransferRepository
|
||||||
|
|
||||||
|
class ConnectedActivityViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
|
private var mTransfersRepository = TransferRepository(application)
|
||||||
|
|
||||||
|
fun observeMissingEquivalentValuesIn(symbol: String) {
|
||||||
|
mTransfersRepository.observeMissingEquivalentValuesIn(symbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCleared() {
|
||||||
|
super.onCleared()
|
||||||
|
mTransfersRepository.onCleared()
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,16 +3,48 @@ package cy.agorise.bitsybitshareswallet.viewmodels
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
|
import com.google.common.primitives.UnsignedLong
|
||||||
|
import cy.agorise.bitsybitshareswallet.database.entities.Transfer
|
||||||
|
import cy.agorise.bitsybitshareswallet.repositories.AssetRepository
|
||||||
import cy.agorise.bitsybitshareswallet.repositories.TransferRepository
|
import cy.agorise.bitsybitshareswallet.repositories.TransferRepository
|
||||||
|
import cy.agorise.graphenej.Asset
|
||||||
|
import cy.agorise.graphenej.AssetAmount
|
||||||
|
import cy.agorise.graphenej.Converter
|
||||||
|
import cy.agorise.graphenej.models.BucketObject
|
||||||
|
|
||||||
|
|
||||||
class TransferViewModel(application: Application) : AndroidViewModel(application) {
|
class TransferViewModel(application: Application) : AndroidViewModel(application) {
|
||||||
private var mRepository = TransferRepository(application)
|
private val TAG = "TransferViewModel"
|
||||||
|
private var mTransferRepository = TransferRepository(application)
|
||||||
|
private var mAssetRepository = AssetRepository(application)
|
||||||
|
|
||||||
internal fun setBlockTime(blockNumber: Long, timestamp: Long) {
|
internal fun setBlockTime(blockNumber: Long, timestamp: Long) {
|
||||||
mRepository.setBlockTime(blockNumber, timestamp)
|
mTransferRepository.setBlockTime(blockNumber, timestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun getTransferBlockNumberWithMissingTime(): LiveData<Long> {
|
internal fun getTransferBlockNumberWithMissingTime(): LiveData<Long> {
|
||||||
return mRepository.getTransferBlockNumberWithMissingTime()
|
return mTransferRepository.getTransferBlockNumberWithMissingTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTransfersWithMissingBtsValue() : LiveData<Transfer> {
|
||||||
|
return mTransferRepository.getTransfersWithMissingBtsValue()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateBtsValue(transfer: Transfer, bucket: BucketObject) {
|
||||||
|
val base = mAssetRepository.getAssetDetails(bucket.key.base.objectId) // Always BTS ?
|
||||||
|
val quote = mAssetRepository.getAssetDetails(bucket.key.quote.objectId) // Any asset other than BTS
|
||||||
|
val converter = Converter(Asset(base.id, base.symbol, base.precision), Asset(quote.id, quote.symbol, quote.precision), bucket)
|
||||||
|
// The "base" amount is always the amount we have, and the quote would be the amount we want to obtain.
|
||||||
|
// It can be strange that the second argument of the AssetAmount constructor here we pass the quote.id, quote.symbol and quote.precision
|
||||||
|
// when building the "base" amount instance. But this is just because the full node will apparently always treat BTS as the base.
|
||||||
|
val baseAmount = AssetAmount(UnsignedLong.valueOf(transfer.transferAmount), Asset(quote.id, quote.symbol, quote.precision))
|
||||||
|
val quoteAmount = converter.convert(baseAmount, Converter.OPEN_VALUE)
|
||||||
|
transfer.btsValue = quoteAmount
|
||||||
|
mTransferRepository.update(transfer)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateBtsValue(transfer: Transfer, value: Long?) {
|
||||||
|
transfer.btsValue = value
|
||||||
|
mTransferRepository.update(transfer)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package cy.agorise.bitsybitshareswallet
|
||||||
|
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
|
import cy.agorise.bitsybitshareswallet.models.coingecko.MarketData
|
||||||
|
import cy.agorise.bitsybitshareswallet.models.coingecko.MarketDataDeserializer
|
||||||
|
import org.junit.Assert
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class MarketDataDeserializerTest {
|
||||||
|
@Test
|
||||||
|
fun marketDataDeserializationTest(){
|
||||||
|
val str = "{\"current_price\": {\"aed\": 0.14139359620401012,\"ars\": 1.476552955052185,\"aud\": 0.05410080634896981,\"bch\": 0.0003021370317928406,\"bdt\": 3.2298217535732276,\"bhd\": 0.01451147244444769,\"bmd\": 0.03849350092032233,\"bnb\": 0.007113127493734956,\"brl\": 0.15000509277539803,\"btc\": 0.00001043269732289735,\"cad\": 0.051866143140042266,\"chf\": 0.03825734329217614,\"clp\": 26.587581916766037,\"cny\": 0.2652895096426772,\"czk\": 0.8706365729081245,\"dkk\": 0.25236393094264586,\"eos\": 0.01566778197589746,\"eth\": 0.0003870069548974383,\"eur\": 0.033804376612212375,\"gbp\": 0.030484350651335475,\"hkd\": 0.3012660745118239,\"huf\": 10.909058160819312,\"idr\": 558.1942568455942,\"ils\": 0.14452962323048843,\"inr\": 2.721290348862006,\"jpy\": 4.327150672205728,\"krw\": 43.47379006939362,\"kwd\": 0.011703102097803801,\"lkr\": 6.939897047172613,\"ltc\": 0.0013225337650442446,\"mmk\": 60.56217136246436,\"mxn\": 0.7738105980956592,\"myr\": 0.1608450935955668,\"nok\": 0.335428517669597,\"nzd\": 0.056803550529088344,\"php\": 2.046274976098886,\"pkr\": 5.3730315641051885,\"pln\": 0.1449376543402434,\"rub\": 2.596498268228413,\"sar\": 0.1444545609036934,\"sek\": 0.3498212376637053,\"sgd\": 0.05281188996415366,\"thb\": 1.2598922851221481,\"try\": 0.20393883733037357,\"twd\": 1.1869880579631216,\"usd\": 0.03849350092032233,\"vef\": 9565.159285292651,\"xag\": 0.002632124388265174,\"xau\": 0.00003094261577979185,\"xdr\": 0.02769368731511483,\"xlm\": 0.3411570542267162,\"xrp\": 0.11074614753363282,\"zar\": 0.5534635980499906}}"
|
||||||
|
val gson = GsonBuilder().registerTypeAdapter(MarketData::class.java, MarketDataDeserializer())
|
||||||
|
.create()
|
||||||
|
val marketData = gson.fromJson<MarketData>(str, MarketData::class.java)
|
||||||
|
Assert.assertEquals(0.03849350092032233, marketData.current_price["usd"])
|
||||||
|
Assert.assertEquals(0.033804376612212375, marketData.current_price["eur"])
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue